简体   繁体   English

CryptoJS AES 加密和 Java AES 解密

[英]CryptoJS AES encryption and Java AES decryption

I'm only asking this because I have read many posts for 2 days now about crypto AES encryption, and just when I thought I was getting it, I realized I wasn't getting it at all.我之所以问这个,是因为我已经阅读了 2 天关于加密 AES 加密的许多帖子,就在我以为我得到它的时候,我意识到我根本没有得到它。

This post is the closest one to my issue, I have exactly the same problem but it is unanswered:这篇文章最接近我的问题,我有完全相同的问题,但没有得到答复:

CryptoJS AES encryption and JAVA AES decryption value mismatch CryptoJS AES 加密和 JAVA AES 解密值不匹配

I have tried doing it in many ways but I haven't gotten it right.我尝试了很多方法,但我没有做对。

First Off第一关

I'm getting the already encrypted string (I only got the code to see how they were doing it), so modifying the encryption way is not an option.我得到了已经加密的字符串(我只得到了代码来看看他们是怎么做的),所以修改加密方式不是一种选择。 That's why all the similar questions aren't that useful to me.这就是为什么所有类似的问题对我来说都没有那么有用。

Second第二

I do have access to the secret key and I can modify it (so adjusting length is an option if neccessary).我确实可以访问密钥并且可以修改它(因此,如果需要,可以选择调整长度)。

The encryption is done on CryptoJS and they send the encrypted string as a GET parameter.加密是在 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();
 });
};

I can easily decrypt this on javascript using CryptoJS with:我可以使用 CryptoJS 在 javascript 上轻松解密它:

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

But I don't want to do this on Javascript, for security reasons, so I'm trying to decrypt this on Java:但出于安全原因,我不想在 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);

Before I had any idea of what I was doing, I tried base64 decoding, adding some IV and a lot of stuff I read, of course none of it worked.在我对自己在做什么之前有任何想法之前,我尝试了 base64 解码,添加了一些 IV 和我阅读的很多东西,当然没有任何效果。

But after I started to understand, kinda, what I was doing, I wrote that simple script above, and got me the same error on the post: Invalid AES key length但是在我开始理解,有点,我在做什么之后,我写了上面那个简单的脚本,并在帖子中得到了同样的错误:无效的 AES 密钥长度

I don't know where to go from here.我不知道从这里去哪里。 After reading a lot about this, the solution seems to be hashing or padding, but I have no control on the encryption method, so I can't really hash the secret or pad it.在阅读了很多关于此的内容后,解决方案似乎是散列或填充,但我无法控制加密方法,因此我无法真正散列秘密或填充它。

But as I said, I can change the secret key so it can match some specific length, and I have tried changing it, but as I'm shooting in the dark here, I don't really know if this is the solution.但正如我所说,我可以更改密钥,以便它可以匹配某个特定长度,并且我尝试更改它,但是由于我在这里黑暗中拍摄,我真的不知道这是否是解决方案。

So, my question basically is, If I got the encrypted string ( in javascript like the first script ) and the secret key, is there a way to decrypt it ( in Java )?所以,我的问题基本上是,如果我得到了加密的字符串(在第一个脚本中的 javascript 中)和密钥,有没有办法解密它(在 Java 中)? If so, how to do it?如果是这样,该怎么做?

Disclaimer: Do not use encryption unless you understand encryption concepts including chaining mode, key derivation functions, IV and block size.免责声明:除非您了解加密概念,包括链接模式、密钥派生函数、IV 和块大小,否则不要使用加密。 And don't roll your own security scheme but stick to an established one.并且不要推出自己的安全方案,而是坚持一个既定的方案。 Just throwing in encryption algorithms doesn't mean an application has become any more secure.仅仅加入加密算法并不意味着应用程序变得更加安全。

CryptoJS implements the same key derivation function as OpenSSL and the same format to put the IV into the encrypted data. CryptoJS 实现了与 OpenSSL 相同的密钥派生功能,并采用相同的格式将 IV 放入加密数据中。 So all Java code that deals with OpenSSL encoded data applies.因此,所有处理 OpenSSL 编码数据的 Java 代码都适用。

Given the following Javascript code:给定以下 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);

We get the cipher text:我们得到密文:

U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=

On the Java side, we have在 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 result is:结果是:

The quick brown fox jumps over the lazy dog. 👻 👻

That's the text we started with.这就是我们开始的文本。 And emojis, accents and umlauts work as well.表情符号、口音和元音变音也可以使用。

GenerateKeyAndIV is a helper function that reimplements OpenSSL's key derivation function EVP_BytesToKey (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c ). 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);
    }
}

Note that you have to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy .请注意,您必须安装 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Otherwise, AES with key size of 256 won't work and throw an exception:否则,密钥大小为 256 的 AES 将不起作用并抛出异常:

java.security.InvalidKeyException: Illegal key size

Update更新

I have replaced Ola Bini's Java code of EVP_BytesToKey , which I used in the first version of my answer, with a more idiomatic and easier to understand Java code (see above).我已经替换了Ola Bini 的 Java 代码EVP_BytesToKey ,我在我的答案的第一个版本中使用了它,用更惯用和更容易理解的 Java 代码(见上文)。

Also see How to decrypt file in Java encrypted with openssl command using AES?另请参阅如何使用 AES 解密使用 openssl 命令加密的 Java 文件? . .

When encrypting on one system and decrypting on another you are at the mercy of system defaults.在一个系统上加密并在另一个系统上解密时,您会受到系统默认值的支配。 If any system defaults do not match (and they often don't) then your decryption will fail.如果任何系统默认值不匹配(并且通常不匹配),那么您的解密将失败。

Everything has to be byte for byte the same on both sides.一切都必须字节对字节在双方相同。 Effectively that means specifying everything on both sides rather than relying on defaults.实际上,这意味着指定双方的所有内容,而不是依赖默认值。 You can only use defaults if you are using the same system at both ends.如果您在两端使用相同的系统,则只能使用默认值。 Even then, it is better to specify exactly.即便如此,最好准确指定。

Key, IV, encryption mode, padding and string to bytes conversion all need to be the same at both ends.两端的密钥、IV、加密方式、填充和字符串到字节的转换都需要相同。 It is especially worth checking that the key bytes are the same.尤其值得检查关键字节是否相同。 If you are using a Key Derivation Function (KDF) to generate your key, then all the parameters for that need to be the same, and hence specified exactly.如果您使用密钥派生函数 (KDF) 来生成您的密钥,则该密钥的所有参数都需要相同,因此必须准确指定。

Your "Invalid AES key length" may well indicate a problem with generating your key.您的“无效 AES 密钥长度”很可能表明生成您的密钥存在问题。 You use getBytes() .您使用getBytes() That is probably an error.那可能是一个错误。 You need to specify what sort of bytes you are getting: ANSI, UTF-8, EBCDIC, whatever.您需要指定获得的字节类型:ANSI、UTF-8、EBCDIC 等等。 The default assumption for the string to byte conversion is the likely cause of this problem.字符串到字节转换的默认假设是此问题的可能原因。 Specify the conversion to be used explicitly at both ends.指定要在两端显式使用的转换。 That way you can be sure that they match.这样您就可以确保它们匹配。

Crypto is designed to fail if the parameters do not match exactly for encryption and decryption.如果加密和解密的参数不完全匹配,加密就会失败。 For example, even a one bit difference in the key will cause it to fail.例如,即使密钥中有一点差异也会导致它失败。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM