CryptoJS AES加密和Java AES解密

22

我只是问这个问题是因为我已经阅读了许多有关加密货币AES加密的帖子,但就在我认为我懂了时,我意识到我根本不懂。

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

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();
 });
};

我可以很容易地使用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中)? 如果有,如何做到?


Javascript端的秘密(secret)是什么样子的?它也是一个字符串吗?它是一个十六进制字符串,例如“34D3724F1A”吗? - Codo
String 不是二进制数据的容器。 - user207421
2个回答

66
免责声明:除非您了解加密概念,包括链接模式、密钥派生函数、IV和块大小,否则不要使用加密。并且不要自己编写安全方案,而是坚持使用已建立的方案。仅仅添加加密算法并不能使应用程序变得更加安全。
CryptoJS实现与OpenSSL相同的密钥派生函数,并使用相同的格式将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.  

这是我们开始使用的文本。表情符号、重音符号和umlauts也可以正常工作。 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)无限强度管辖策略。否则,使用256位密钥的AES将无法工作并抛出异常:
java.security.InvalidKeyException: Illegal key size

更新

我已经用更符合惯用语言且更易理解的Java代码(见上文)替换了我在第一个版本答案中使用的Ola Bini的Java代码中的EVP_BytesToKey

另请参见如何使用AES解密使用openssl命令在Java中加密的文件?


让我再解释一下,这是一个NetBeans项目,我发现在项目属性中,在源代码选项卡中,源代码/二进制格式设置为JDK 7版。不知道这是否有影响,但在我的机器上,我安装了两个JVM,Java 8、OpenJDK和Oracle,而Oracle正在运行。我不知道项目的源代码为何是JDK 7。 - Lauro182
提出一个新的问题。 - Codo
4
你如何使用Java加密相同的数据?你能发布一下加密代码吗? - Amarjit
它的Java版本是否可用于加密?请做必要的事情。 - Harneet Kaur
{btsdaf} - Arunabh
显示剩余21条评论

3
在一个系统上加密,在另一个系统上解密时,您要受到系统默认设置的限制。如果任何系统默认设置不匹配(而它们经常不匹配),则您的解密将失败。
两端必须完全一致。这实际上意味着在两端都指定所有内容,而不是依赖默认值。只有在两端使用相同的系统时才能使用默认值。即使如此,最好也要精确指定。
密钥、IV、加密模式、填充和字符串转换为字节的方式在两端都需要保持一致。特别值得注意的是,关键字节是否相同。如果您正在使用密钥派生函数(KDF)生成密钥,则该函数的所有参数都需要相同,并且需要精确指定。
您的“无效AES密钥长度”很可能表示生成密钥时出现问题。您使用了getBytes()。那很可能是个错误。您需要指定要获取的字节类型:ANSI、UTF-8、EBCDIC等。字符串转换为字节的默认假设可能是问题的根源。在两端明确指定要使用的转换方式,这样您就可以确信它们是匹配的。
加密和解密的参数如果不完全匹配,加密技术将会失效。例如,即使密钥中只有一位不同,它也会导致加密失败。

请问您能帮助我们吗?我们需要在Ionic 3前端使用AES 256加密算法,并在Java层级解密。但是,加密后的数据无法解密。详情请参考:https://stackoverflow.com/questions/63811173/ionic-v3-aes-256-algorithm-to-using-encrypted-not-able-to-decrypt-in-java-aes-gc - Developer KE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接