CryptoJS AES加密和Java AES解密

我只是问这个,因为我已经阅读了很多关于加密AES加密的post2天了,就在我认为我得到它的时候,我意识到我根本没有得到它。

这篇文章是我最接近我的问题,我有完全相同的问题,但没有答案:

CryptoJS AES加密和JAVA AES解密值不匹配

我试过很多方面,但我没有把它做对。

首先

我正在获取已经加密的字符串(我只有代码才能看到它们是如何做的),因此修改加密方式不是一种选择。 这就是为什么所有类似的问题对我都没有用。

第二

我有权访问密钥,我可以修改它(所以如果需要,调整长度是一个选项)。

加密是在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(); }); }; 

我可以使用CryptoJS在javascript上轻松解密这个:

 var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret); console.log(decrypted.toString(CryptoJS.enc.Utf8)); 

但出于安全考虑,我不想在Javascript上这样做,所以我试图在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和我读过的很多东西,当然没有一个有效。

但是在我开始理解,有点,我在做什么之后,我在上面编写了这个简单的脚本,并在post上得到了同样的错误: AES密钥长度无效

我不知道从哪里开始。 在阅读了很多相关内容之后,解决方案似乎是散列或填充,但我无法控制加密方法,所以我无法真正散列秘密或填充它。

但正如我所说,我可以更改密钥,以便它可以匹配一些特定长度,我已经尝试改变它,但是当我在黑暗中拍摄时,我真的不知道这是否是解决方案。

所以,我的问题基本上是,如果我得到加密的字符串( 在第一个脚本的javascript中 )和密钥,有没有办法解密它( 用Java )? 如果是这样,怎么办?

CryptoJS实现与OpenSSL相同的密钥派生function以及将IV放入加密数据的相同格式。 因此,所有处理OpenSSL编码数据的Java代码都适用。

给出以下Javascript代码:

 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. * 

* 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. *

* @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
null) * @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 Cryptography Extension(JCE) Unlimited Strength Jurisdiction Policy 。 否则,密钥大小为256的AES将无法工作并抛出exception:

 java.security.InvalidKeyException: Illegal key size 

更新

我已经取代了Ola Bini的 EVP_BytesToKey 的Java代码 ,我在我的答案的第一个版本中使用了它,它具有更惯用且更易于理解的Java代码(见上文)。

另请参阅如何解密使用AES使用openssl命令加密的Java文件? 。

在一个系统上加密并在另一个系统上解密时,您将受系统默认值的限制。 如果任何系统默认值不匹配(并且它们通常不匹配),那么您的解密将失败。

两边的所有内容都必须是字节相同的。 实际上,这意味着指定双方的所有内容而不是依赖于默认值。 如果您在两端使用相同的系统,则只能使用默认值。 即便如此,最好准确指定。

密钥,IV,加密模式,填充和字符串到字节的转换都需要在两端相同。 特别值得检查的是关键字节是否相同。 如果您使用密钥派生函数(KDF)生成密钥,那么所有参数必须相同,因此要精确指定。

您的“无效的AES密钥长度”可能表示生成密钥时出现问题。 你使用getBytes() 。 这可能是一个错误。 您需要指定获得的字节类型:ANSI,UTF-8,EBCDIC等等。 字符串到字节转换的默认假设是导致此问题的可能原因。 指定要在两端显式使用的转换。 这样你就可以确定它们是匹配的。

如果参数与加密和解密不完全匹配,则加密被设计为失败。 例如,即使密钥中的一位差异也会导致其失败。