【问题标题】:CryptoJS decrypting (AES) a file bytearray coming from JavaCryptoJS 解密 (AES) 来自 Java 的文件字节数组
【发布时间】:2016-04-25 11:27:53
【问题描述】:

我正在用 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: 我已经解决了这个问题。我使用的代码作为答案发布。

【问题讨论】:

  • "mysecretmysecret" 如果它位于客户端源代码中,它可能不是那个秘密。
  • @AlexK。这只是一个 POC。还没有进入真正的代码。
  • 不要使用字符串作为键,使用字节数组。从字符串到字节数组的转换是不明确的(BoM 开头?\0 终止符?等等)尤其是在系统之间传输加密数据时,依赖系统默认值。显式设置模式、IV/Nonce 等。您对此几乎没有做任何事情,并且依赖默认值在系统之间保持相同。任何不匹配的默认值都会导致问题。
  • @rossum 在 CryptoJS(至少 Angular 6)中使用字节数组是一场噩梦。你有工作样本吗?
  • @afe 我帮不上什么忙,因为我不使用 CryptoJS 或 Angular。也许专门为他们询问一个小组?

标签: java encryption cryptography aes cryptojs


【解决方案1】:

所以我终于解决了这个问题。感谢 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。

【讨论】:

  • 喜欢这个作为起点。匹配“wordArray”的“arraybuffer”挽救了这一天。 decrypted.toString(CryptoJS.enc.Utf8) 可以显示原文。
【解决方案2】:

让我们回顾一下,您正在使用的 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 模式,因此您必须在页面中的主要 CryptoJS 文件之后包含 JavaScript file
此外,默认情况下 CryptoJS 不处理 ArrayBuffer。您需要为此添加shim(来源:this answer)。


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

  • 切勿使用ECB mode。它是确定性的,因此在语义上不安全。您至少应该使用像CBCCTR 这样的随机模式。 IV/nonce 不是秘密的,因此您可以将其与密文一起发送。一种常见的方法是放在密文前面。

  • 最好对您的密文进行身份验证,这样就不会像padding oracle attack 这样的攻击。这可以通过 GCM 或 EAX 等经过身份验证的模式或encrypt-then-MAC 方案来完成。

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

【讨论】:

  • 感谢@Artjom。这只是一个 POC,我需要使用尽可能少的代码来解决这个问题。当然我知道欧洲央行是最不安全的,但我会再次研究强化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-20
  • 2021-08-13
  • 1970-01-01
  • 2019-08-13
  • 1970-01-01
  • 2014-08-17
  • 2023-04-04
相关资源
最近更新 更多