CryptoJS 解密 (AES) 来自 Java 的文件字节数组

CryptoJS decrypting (AES) a file bytearray coming from Java

我正在 Java 中加密一个文件,需要在客户端对其进行解密。 这是服务器端代码:

Key secretKey = new SecretKeySpec("mysecretmysecret".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] outputBytes = cipher.doFinal(read(sampleFile));
return outputBytes;

在客户端,我使用 Ajax 请求来获取文件并使用 CryptoJS AES:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'file', true);
xhr.responseType = 'arraybuffer';

xhr.onload = function (e) {
        var encryptedData = this.response;
        var decrypted = CryptoJS.AES.decrypt(encryptedData, "mysecretmysecret");
        console.log(decrypted);
};
xhr.send();

但这并不能解密文件。我在控制台中将其打印为解密的值:

W…y.init {words: Array[0], sigBytes: 0}

我也尝试过将 arraybuffer 转换为建议的 WordArray here,但结果仍然相同。 如果有人能指出正确的方向并告诉我哪里做错了,我将非常高兴。

编辑 1: 我已经解决了这个问题。我使用的代码作为答案发布。

让我们回顾一下,在 Java 你正在使用

  • AES,
  • ECB(未指定但通常是默认值;它不安全!),
  • PKCS#7 填充(未指定但通常是默认值;它与 PKCS#5 填充相同),并且
  • 16个字符的密码作为16个字节的密钥(取决于默认的系统编码)。

如果密钥作为字符串传递给 CryptoJS,它将必须使用 OpenSSL 的 EVP_BytesToKey 和单轮 MD5 从假定的密码派生密钥。由于您的密文未以 OpenSSL 兼容格式编码,因此它会失败。问题是,你不需要那个。

以下代码可以正确解密来自 Java 的密文,但不是很安全:

var passwordWords = CryptoJS.enc.Utf8.parse("mysecretmysecret");
var decrypted = CryptoJS.AES.decrypt({
    ciphertext: CryptoJS.lib.WordArray.create(encryptedData) // or use some encoding
}, passwordWords, {
    mode: CryptoJS.mode.ECB
});
console.log(decrypted.toString(CryptoJS.enc.Utf8)); // in case the plaintext is text

基本汇总中不包含 ECB 模式,因此您必须在页面中将 JavaScript file 包含在主 CryptoJS 文件之后。
此外,CryptoJS 默认不处理 ArrayBuffer。您需要包含 shim for that (source: this answer).


问题在于它的不安全性。 ECB 模式非常不安全。

  • 永远不要使用 ECB mode. It's deterministic and therefore not semantically secure. You should at the very least use a randomized mode like CBC or CTR。 IV/nonce 不是秘密的,因此您可以将它与密文一起发送。一种常见的方法是将其放在密文的前面。

  • 最好对您的密文进行身份验证,以便像 padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC 方案一样进行攻击。

  • 密钥可以从密码中导出,但是应该使用适当的方案,例如 PBKDF2。 Java 和 CryptoJS 都支持这些。

所以我终于解决了这个问题。感谢 Artjom 指出正确的方向。 我已经更改了我的 Java 代码以将 CBC 与 PKCS5Padding 一起使用。

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec("mysecretmysecret".getBytes(), "AES");
IvParameterSpec IVKey = new IvParameterSpec("mysecretmysecret".getBytes());
cipher.init(Cipher.ENCRYPT_MODE, myKey, IVKey);
byte[] outputBytes = cipher.doFinal(read(sampleFile));
return outputBytes;

我的 javascript 是这样的:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'file', true);
xhr.responseType = 'arraybuffer';

xhr.onload = function(e) {
    var encryptedData = this.response;
    var passwordWords = CryptoJS.enc.Utf8.parse("mysecretmysecret"); //yes I know, not a big secret!
    var wordArray = CryptoJS.lib.WordArray.create(encryptedData);
    var decrypted = CryptoJS.AES.decrypt({
        ciphertext: wordArray
    }, passwordWords, {
        iv: passwordWords, //yes I used password as iv too. Dont mind.
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    console.log(decrypted); //Eureka!!
};

xhr.send();

decrypted 是一个 WordArray。