使用 AES256 和 Node.js 解密长度超过 15 个字符的输入数据时出错

Error when decrypt input data longer than 15 chars with AES256 and Node.js

我正在使用 Node.js 的加密模块和 AES-256-CBC 密码算法编写自己的安全程序 class。

但是当我尝试解密从超过 15 个字符的输入数据加密的加密字符串时,失败并出现此错误:

crypto.js:153
var ret = this._handle.final();
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

我认为问题出在加密或 IV 生成上,事实上,加密的十六进制字符串总是 32 个字符长。

让我们一起回顾一下代码:

var crypto = require("crypto"),
    password = "mySecureKey",
    salt = "mySaltKey";

//generate the IV
crypto.pbkdf2(password , salt, 4096, 8, "sha1", function(err, key) {
    if (err) throw err;   
    var cipher_iv = new Buffer(key.toString('hex'));

    //encrypt the string
    var input = "helloPrettyWorld";
    cipher = crypto.createCipheriv("aes-256-cbc", new Buffer(password), cipher_iv);
    cipher.update(input, "utf8", "hex");
    var encrypted = cipher.final("hex"); //i.e: input = "hello"; encrypted = "2300743605fbdaf0171052ccc6322e96"

    //decrypt the string
    cipher = crypto.createDecipheriv("aes-256-cbc", new Buffer(password), cipher_iv); /* THE ERROR IS THROWN HERE */
    cipher.update(encrypted, "hex", "utf8")     
    var decrypted = cipher.final("utf8");
});

我尝试调整 password/salt 长度,甚至使用固定长度的字符串(32、16 等),但没有解决问题。

回顾:

像"helloNiceWorld"(14个字符)这样的输入数据将被完美加密和解密,而像"helloPrettyWorld"(16个字符)这样的输入数据则不会。

TL;DR 你不使用 cipher.update().

的结果就丢掉了一部分明文和密文

AES 是一种块密码,仅适用于 16 字节(块大小)的块。 CBC 模式将其扩展到长度为块大小的倍数的明文。然后需要填充(默认为 PKCS#7 填充)将明文填充到块大小的下一个倍数。

这意味着填充是完成加密之前的最后操作的一部分。填充应用于 cipher.final() 函数。

cipher.update() return是密文的一部分,但不处理(它在内部缓存)最后一个字节以应用填充。在加密结束时,必须调用 cipher.final() 以便将填充应用于最后缓存的字节并进行加密。

由于 PKCS#7 填充总是添加填充,当明文长度为 16 到 31 字节时,您将得到两块填充的明文。现在,问题是您没有存储导致密文不完整的 cipher.update() 调用的结果。如果您的消息小于单个块,那么 cipher.update() 会 return 一些空的东西(字符串或缓冲区),您将从 cipher.final().

获得完整的密文

不要忘记连接不同的密文和明文部分:

cipher = crypto.createCipheriv("aes-256-cbc", new Buffer(password), cipher_iv);
var encrypted = cipher.update(input, "utf8", "hex");
encrypted += cipher.final("hex");

//decrypt the string
cipher = crypto.createDecipheriv("aes-256-cbc", new Buffer(password), cipher_iv);
var decrypted = cipher.update(encrypted, "hex", "utf8")     
decrypted += cipher.final("utf8");

其他注意事项:

password = "mySecureKey" 如果这确实是一些文本,那么它是不安全的,也不是密钥,但正如变量名所说,它是一个密码。不要直接使用密码作为密钥。使用随机生成的盐(每个密码)从密码中导出密钥。

此外,在每次加密时生成一个新的随机IV,并将其简单地放在密文前面。 IV 不必是秘密的,但它必须是不可预测的。如果您重新使用 IV,那么观察您的密文的攻击者可能会确定您是否再次发送之前发送的消息。如果您使用随机 IV,您将获得语义安全。

此外,使用像 HMAC-SHA256 这样具有强 MAC 的加密然后 MAC 方案验证您的密文。这使您能够检测对密文的任何(恶意)修改,并防止诸如 padding-oracle 攻击之类的攻击。