Java 等效于 OpenSSL AES CBC 加密
Posted
技术标签:
【中文标题】Java 等效于 OpenSSL AES CBC 加密【英文标题】:Java equivalent of an OpenSSL AES CBC encryption 【发布时间】:2015-12-07 03:48:29 【问题描述】:我不是密码学专业人士,特别是由于 OpenSSL 缺少很多文档,我不确定如何解决这个问题。
我有一个期望接收加密消息的外部系统。提供的唯一示例以这种方式使用 OpenSSL:
$ openssl enc -aes-256-cbc -a -in t.txt -k testpass
U2FsdGVkX1/RUdaSJKRXhHv3zUyTsQwu5/ar2ECKDlrNyH5GL4xRR4fgxkiWqkS1
cQstcoSIgWfRPSOFj/5OtdNLeNXiVR6MxSKJ+NvS9LyUD8+Rg6XIcYUvxR4gHi3w
DWT44LAMCpRAh1Q0t4Z2g7rwb0D05T6ygLaWvB5zD/xGZD3brTqSlWmiJb9Imgda
M6soZO7BhbYdqWqEUl5r6+EbkD21f6L3NX3hJFo+BJ+VFctiAlBO8NwT5l4ogo/s
GErm8gqRr57XoX/kvKAimg==
t.txt
文件在其中一行包含此字符串:
AMOUNT=10&TID=#19:23&CURRENCY=EUR&LANGUAGE=DE&SUCCESS_URL=http://some.url/sucess&ERROR_URL=http://some.url/error&CONFIRMATION_URL=http://some.url/confirm&NAME=customer full name`
我找到了this 其他问题,我已经能够使用以下代码进行加密:
String password = "passPhrase";
String salt = "15charRandomSalt";
int iterations = 100;
/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(Charset.forName("UTF8")), iterations, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] cipherText = cipher.doFinal(toBeEncrypted.getBytes("UTF-8"));
encryptedData = Base64.getEncoder().encodeToString(cipherText);
encryptedData += Base64.getEncoder().encodeToString(iv);
我无法理解的是我应该如何生成与 OpenSSL 类似的输出(加密数据)。我有盐、iv 和 cipherText,OpenSSL 输出 Base64 编码结果是这些连接的结果吗?还是只有其中一个?
在加密之前我与其他系统共享的唯一内容是密码短语。如果他们不知道盐和迭代次数,他们如何解密结果?
谁能回答那些未知参数,并告诉我上面的代码是否相当于OpenSSL过程?
【问题讨论】:
OpenSSL commandlineenc
默认执行 PBE,但您可以使用 -K
(必须为大写)和 -iv
(如果适用)进行“原始”加密,请参见手册页。 OpenSSL 库分别提供密码和 PBKDF,程序可以根据需要使用。
此外,如果您(可以)使用BouncyCastle.org 第三方提供商,它已经为每个 PBEWITHMD5AND128,192,256BITAES-CBC-OPENSSL 编写了 JCA 密钥工厂和密码。你仍然需要做文件头和base64。
我不需要做的是改变 OpenSSL 进程。我实际上必须在 java 中重建相同的过程。我会看看 BouncyCastle.org API。
@momnag 如果您可以添加文件 t.txt,我们可以确定加密是如何工作的。
@JonathanRosenne 使用示例加密的数据和结果更新了问题。
【参考方案1】:
您可以查看this discussion,将密钥生成算法指定为两个 MD5 哈希的串联。
关于那里提到的盐,opensssl enc man page 说:
当使用 salt 时,加密后的前 8 个字节 数据是为盐保留的:它是随机生成的 加密文件并从加密文件中读取 解密。
【讨论】:
在生成密钥和IV(用于CBC)并用它们加密明文之后,-a
base64 对密文进行编码。
密钥生成算法需要自己写吗?我刚刚添加了一段代码来提问,它可能应该正确地完成所有加密过程,但是我缺少的是如何用盐、IV 和密文构建 base64 编码输出。【参考方案2】:
以下是解密上述OPENSSL加密的Java程序(需要Java 8):
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Decoder;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class TestAesDecrypt
public static void main(final String[] args) throws Exception
final byte[] pass = "testpass".getBytes(StandardCharsets.US_ASCII);
final byte[] magic = "Salted__".getBytes(StandardCharsets.US_ASCII);
final String inFile = "e:/t/e.txt";
String source = new String(Files.readAllBytes(Paths.get(inFile)),
StandardCharsets.US_ASCII);
source = source.replaceAll("\\s", "");
final Decoder decoder = Base64.getDecoder();
final byte[] inBytes = decoder.decode(source);
final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0,
magic.length);
if (!Arrays.equals(shouldBeMagic, magic))
System.out.println("Bad magic number");
return;
final byte[] salt = Arrays.copyOfRange(inBytes, magic.length,
magic.length + 8);
final byte[] passAndSalt = concat(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3; i++)
final byte[] data = concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(data);
keyAndIv = concat(keyAndIv, hash);
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
final String clearText = new String(clear, StandardCharsets.ISO_8859_1);
System.out.println(clearText);
private static byte[] concat(final byte[] a, final byte[] b)
final byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
【讨论】:
我实际上是在寻找 Java 中的加密(等效于 openSSL),但根据您的回答,我能够生成该过程,因此我将其标记为答案。 @mohamnag - 我不认为你会考虑分享你的加密代码?我发现了一些其他较旧的实现,但将其作为答案的一部分会很方便。 @Robert 我的代码的问题在于它是专门为非常有限的输入类型而设计的,无论如何它后来被第 3 方提供的库所取代。我认为加密代码应该经过彻底的测试,因此分享一半使用过的代码可能不是一个好主意 您好,我使用上述加密方法将加密数据重定向到java中的文件。密码是“testpass”。但是当我使用 openssl 命令解密它时,我得到“坏幻数”错误。这里是命令:“openssl aes-256-cbc -d -in input.crypt -out output.txt -k testpass” 如果命令错误,请提出建议。我对如何在命令中添加盐也有点困惑。【参考方案3】:这个问题有一个已被接受的答案,但它有点老了,但这似乎是一次又一次出现的问题。我有 2 个项目,我们与第 3 方通信,密码是带有预共享密钥的 OpenSSL AES。
我使用了 not-yet-common-ssl 库。然而,它似乎停留在 0.3.x 版本,近 2 年没有发布,没有任何邮件列表流量或可见的开发,我不得不得出结论,这基本上已经死了。
基于一些额外的 *** 问题,我确实发现了 Spring Security 和 Encryptor4j 两者似乎都提供了一些合理打包的文本编码。然而,试图让 Spring Security 的加密器对已知的编码文本字符串进行解码对我来说失败了,我猜 OpenSSL 使用的 IV 和密钥生成在提供的实现中根本不支持。
通过检查上面的代码以及已知的 C# 和 php 实现,我能够提出一个实用程序类,该类目前正在通过我的互操作性测试。一般来说,我更喜欢使用一个已知的库,但如果有的话,我一直无法找到它。类(https://gist.github.com/rrsIPOV/4d0f6be7c58173c16e9edf9f97c7d7f2)如下:
import groovy.transform.CompileStatic;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import static java.nio.charset.StandardCharsets.*
/**
* Mimics the OpenSSL AES Cipher options for encrypting and decrypting messages using a shared key (aka password) with symetric ciphers.
*/
@CompileStatic
class OpenSslAes
/** OpenSSL's magic initial bytes. */
private static final String SALTED_STR = "Salted__";
private static final byte[] SALTED_MAGIC = SALTED_STR.getBytes(US_ASCII);
static String encryptAndURLEncode(String password, String clearText)
String encrypted = encrypt(password, clearText);
return URLEncoder.encode(encrypted, UTF_8.name() );
/**
*
* @param password The password / key to encrypt with.
* @param data The data to encrypt
* @return A base64 encoded string containing the encrypted data.
*/
static String encrypt(String password, String clearText)
final byte[] pass = password.getBytes(US_ASCII);
final byte[] salt = (new SecureRandom()).generateSeed(8);
final byte[] inBytes = clearText.getBytes(UTF_8);
final byte[] passAndSalt = array_concat(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++)
final byte[] hashData = array_concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(hashData);
keyAndIv = array_concat(keyAndIv, hash);
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] data = cipher.doFinal(inBytes);
data = array_concat(array_concat(SALTED_MAGIC, salt), data);
return Base64.getEncoder().encodeToString( data );
/**
* @see http://***.com/questions/32508961/java-equivalent-of-an-openssl-aes-cbc-encryption for what looks like a useful answer. The not-yet-commons-ssl also has an implementation
* @param password
* @param source The encrypted data
* @return
*/
static String decrypt(String password, String source)
final byte[] pass = password.getBytes(US_ASCII);
final byte[] inBytes = Base64.getDecoder().decode(source);
final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, SALTED_MAGIC.length);
if (!Arrays.equals(shouldBeMagic, SALTED_MAGIC))
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
final byte[] salt = Arrays.copyOfRange(inBytes, SALTED_MAGIC.length, SALTED_MAGIC.length + 8);
final byte[] passAndSalt = array_concat(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < 48; i++)
final byte[] hashData = array_concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(hashData);
keyAndIv = array_concat(keyAndIv, hash);
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
return new String(clear, UTF_8);
private static byte[] array_concat(final byte[] a, final byte[] b)
final byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
【讨论】:
感谢您提供完整的解决方案。这非常有帮助。我不得不将它降级到 java 7,因此 Base64 的东西被转换为用户来自***.com/questions/14413169/… 的答案。这将对使用旧版 Java 的其他人有所帮助 您的代码非常有用。我复制了您的代码并对其进行了增强以支持 ECB 模式、128 位和 256 位 AES,有和没有 PBKDF2,它可以在此处获得:gist.github.com/thiamteck/798343b9e4a5d7df748746d995eba53e【参考方案4】:此时openssl版本1.1.0f-3需要一个摘要函数SHA-256。没有这个就无法解码。
【讨论】:
以上是关于Java 等效于 OpenSSL AES CBC 加密的主要内容,如果未能解决你的问题,请参考以下文章
如何使用java解码用openssl aes-128-cbc编码的字符串?
AES (aes-cbc-128, aes-cbc-192, aes-cbc-256) 使用 openssl C 加密/解密
OpenSSL -aes-256-cbc解密行为在1.0.2o和1.1.0g之间变化
php openssl aes-256-cbc key长度自动匹配了128的长度,为啥