CryptoJS AES 加密和 Java AES 解密
CryptoJS AES encryption and Java AES decryption
我问这个只是因为我已经阅读了很多 posts 关于加密 AES 加密的文章 2 天了,就在我以为我明白了的时候,我意识到我并没有得到它全部。
这个 post 是最接近我的问题的一个,我有完全相同的问题但没有答案:
CryptoJS AES encryption and JAVA AES decryption value mismatch
我试过很多方法,但我做对了。
首发
我得到的是已经加密的字符串(我只是得到代码看看他们是怎么做的),所以修改加密方式不是一个选项。这就是为什么所有类似的问题对我都没有那么有用。
第二
我确实可以访问密钥并且我可以修改它(因此如果需要调整长度是一个选项)。
加密是在 CryptoJS 上完成的,他们将加密的字符串作为 GET 参数发送。
GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
return new Promise((resolve, reject) => {
const currentDateInMilliseconds = new Date().getTime();
const secret = tokenSecret.secret;
var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
encrypted = encrypted.toString();
self.urlParams = {
token: encrypted,
time: currentDateInMilliseconds
};
resolve();
});
};
我可以在 javascript 上使用 CryptoJS 轻松解密:
var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
但出于安全原因,我不想在 Java 脚本上执行此操作,因此我尝试在 Java:
上解密它
String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);
在我对自己在做什么有任何想法之前,我尝试了 base64 解码,添加了一些 IV 和我读过的很多东西,当然 none 它起作用了。
但是在我开始明白我在做什么之后,我在上面写了那个简单的脚本,并在 post 上得到了同样的错误:无效的 AES 密钥长度
我不知道从这里到哪里去。在阅读了很多关于此的内容之后,解决方案似乎是散列或填充,但我无法控制加密方法,所以我无法真正散列或填充秘密。
但正如我所说,我可以更改密钥,使其可以匹配特定长度,我已经尝试更改它,但由于我是在黑暗中拍摄,我真的不知道这是否可行是解决方案。
所以,我的问题基本上是,如果我得到加密字符串(in javascript,就像第一个脚本)和密钥,有没有办法解密它(in Java)?如果可以,怎么做?
当在一个系统上加密并在另一个系统上解密时,您将受制于系统默认设置。如果任何系统默认值不匹配(通常不匹配),那么您的解密将失败。
一切都必须byte for byte两边都一样。实际上,这意味着在双方都指定所有内容,而不是依赖默认值。如果两端使用相同的系统,则只能使用默认值。即便如此,最好准确指定。
密钥、IV、加密模式、填充和字符串到字节的转换都需要在两端相同。特别值得检查的关键字节是相同的。如果您使用密钥派生函数 (KDF) 生成密钥,则所有参数都必须相同,因此要准确指定。
您的 "Invalid AES key length" 很可能表明生成密钥时出现问题。您使用 getBytes()
。这可能是一个错误。您需要指定要获取的字节类型:ANSI、UTF-8、EBCDIC 等等。字符串到字节转换的默认假设可能是导致此问题的原因。指定要在两端显式使用的转换。这样您就可以确定它们匹配。
如果加密和解密的参数不完全匹配,加密就会失败。例如,即使密钥有一位差异也会导致它失败。
免责声明:除非您了解包括链接模式、密钥派生函数、IV 和块大小在内的加密概念,否则请勿使用加密。并且不要推出自己的安全方案,而要坚持既定的方案。仅仅引入加密算法并不意味着应用程序变得更加安全。
CryptoJS实现了与OpenSSL相同的密钥推导功能,同样的格式将IV放入加密数据中。因此,所有处理 OpenSSL 编码数据的 Java 代码都适用。
给定以下 Java脚本代码:
var text = "The quick brown fox jumps over the lazy dog. ";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);
我们得到密文:
U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=
在Java这边,我们有
String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";
byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
结果是:
The quick brown fox jumps over the lazy dog.
这是我们开始的文字。表情符号、重音符号和变音符号也可以使用。
GenerateKeyAndIV
是一个辅助函数,它重新实现了OpenSSL的密钥推导函数EVP_BytesToKey
(参见https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c)。
/**
* Generates a key and an initialization vector (IV) with the given salt and password.
* <p>
* This method is equivalent to OpenSSL's EVP_BytesToKey function
* (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
* By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
* </p>
* @param keyLength the length of the generated key (in bytes)
* @param ivLength the length of the generated IV (in bytes)
* @param iterations the number of digestion rounds
* @param salt the salt data (8 bytes of data or <code>null</code>)
* @param password the password data (optional)
* @param md the message digest algorithm to use
* @return an two-element array with the generated key and IV
*/
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
请注意,您必须安装 Java 加密扩展 (JCE) Unlimited Strength Jurisdiction Policy。否则,密钥大小为 256 的 AES 将无法工作并抛出异常:
java.security.InvalidKeyException: Illegal key size
更新
我已经将我在第一版答案中使用的 EVP_BytesToKey
的 Ola Bini's Java code 替换为更惯用且更易于理解的 Java 代码(见上文)。
另见 How to decrypt file in Java encrypted with openssl command using AES?。
我问这个只是因为我已经阅读了很多 posts 关于加密 AES 加密的文章 2 天了,就在我以为我明白了的时候,我意识到我并没有得到它全部。
这个 post 是最接近我的问题的一个,我有完全相同的问题但没有答案:
CryptoJS AES encryption and JAVA AES decryption value mismatch
我试过很多方法,但我做对了。
首发
我得到的是已经加密的字符串(我只是得到代码看看他们是怎么做的),所以修改加密方式不是一个选项。这就是为什么所有类似的问题对我都没有那么有用。
第二
我确实可以访问密钥并且我可以修改它(因此如果需要调整长度是一个选项)。
加密是在 CryptoJS 上完成的,他们将加密的字符串作为 GET 参数发送。
GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
return new Promise((resolve, reject) => {
const currentDateInMilliseconds = new Date().getTime();
const secret = tokenSecret.secret;
var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
encrypted = encrypted.toString();
self.urlParams = {
token: encrypted,
time: currentDateInMilliseconds
};
resolve();
});
};
我可以在 javascript 上使用 CryptoJS 轻松解密:
var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
但出于安全原因,我不想在 Java 脚本上执行此操作,因此我尝试在 Java:
上解密它String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);
在我对自己在做什么有任何想法之前,我尝试了 base64 解码,添加了一些 IV 和我读过的很多东西,当然 none 它起作用了。
但是在我开始明白我在做什么之后,我在上面写了那个简单的脚本,并在 post 上得到了同样的错误:无效的 AES 密钥长度
我不知道从这里到哪里去。在阅读了很多关于此的内容之后,解决方案似乎是散列或填充,但我无法控制加密方法,所以我无法真正散列或填充秘密。
但正如我所说,我可以更改密钥,使其可以匹配特定长度,我已经尝试更改它,但由于我是在黑暗中拍摄,我真的不知道这是否可行是解决方案。
所以,我的问题基本上是,如果我得到加密字符串(in javascript,就像第一个脚本)和密钥,有没有办法解密它(in Java)?如果可以,怎么做?
当在一个系统上加密并在另一个系统上解密时,您将受制于系统默认设置。如果任何系统默认值不匹配(通常不匹配),那么您的解密将失败。
一切都必须byte for byte两边都一样。实际上,这意味着在双方都指定所有内容,而不是依赖默认值。如果两端使用相同的系统,则只能使用默认值。即便如此,最好准确指定。
密钥、IV、加密模式、填充和字符串到字节的转换都需要在两端相同。特别值得检查的关键字节是相同的。如果您使用密钥派生函数 (KDF) 生成密钥,则所有参数都必须相同,因此要准确指定。
您的 "Invalid AES key length" 很可能表明生成密钥时出现问题。您使用 getBytes()
。这可能是一个错误。您需要指定要获取的字节类型:ANSI、UTF-8、EBCDIC 等等。字符串到字节转换的默认假设可能是导致此问题的原因。指定要在两端显式使用的转换。这样您就可以确定它们匹配。
如果加密和解密的参数不完全匹配,加密就会失败。例如,即使密钥有一位差异也会导致它失败。
免责声明:除非您了解包括链接模式、密钥派生函数、IV 和块大小在内的加密概念,否则请勿使用加密。并且不要推出自己的安全方案,而要坚持既定的方案。仅仅引入加密算法并不意味着应用程序变得更加安全。
CryptoJS实现了与OpenSSL相同的密钥推导功能,同样的格式将IV放入加密数据中。因此,所有处理 OpenSSL 编码数据的 Java 代码都适用。
给定以下 Java脚本代码:
var text = "The quick brown fox jumps over the lazy dog. ";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);
我们得到密文:
U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=
在Java这边,我们有
String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";
byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
结果是:
The quick brown fox jumps over the lazy dog.
这是我们开始的文字。表情符号、重音符号和变音符号也可以使用。
GenerateKeyAndIV
是一个辅助函数,它重新实现了OpenSSL的密钥推导函数EVP_BytesToKey
(参见https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c)。
/**
* Generates a key and an initialization vector (IV) with the given salt and password.
* <p>
* This method is equivalent to OpenSSL's EVP_BytesToKey function
* (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
* By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
* </p>
* @param keyLength the length of the generated key (in bytes)
* @param ivLength the length of the generated IV (in bytes)
* @param iterations the number of digestion rounds
* @param salt the salt data (8 bytes of data or <code>null</code>)
* @param password the password data (optional)
* @param md the message digest algorithm to use
* @return an two-element array with the generated key and IV
*/
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
请注意,您必须安装 Java 加密扩展 (JCE) Unlimited Strength Jurisdiction Policy。否则,密钥大小为 256 的 AES 将无法工作并抛出异常:
java.security.InvalidKeyException: Illegal key size
更新
我已经将我在第一版答案中使用的 EVP_BytesToKey
的 Ola Bini's Java code 替换为更惯用且更易于理解的 Java 代码(见上文)。
另见 How to decrypt file in Java encrypted with openssl command using AES?。