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

时间:2015-11-07 18:10:36

标签: node.js encryption cryptography aes

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

但是当我尝试解密从超过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");
});

我尝试调整密码/盐长度,甚至使用固定长度的字符串(32,16等等),但不解决问题。

回顾:

输入数据如:“helloNiceWorld”(14个字符)将被完美加密和解密,而输入数据如“helloPrettyWorld”(16个字符)则不会。

1 个答案:

答案 0 :(得分:8)

TL; DR 您不使用cipher.update()的结果丢弃了部分明文和密文。

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

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

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

由于PKCS#7填充总是添加填充,当明文长度为16到31个字节时,您将得到两个块填充的明文。现在,问题是您没有存储cipher.update()调用的结果,导致不完整的密文。如果您的消息小于单个块,则cipher.update()将返回空(字符串或缓冲区),您将从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,您将获得语义安全性。

此外,使用像MACAC-SHA256这样强大的MAC加密然后MAC方案验证您的密文。这使您能够检测到密文的任何(恶意)修改,并防止诸如padding-oracle攻击之类的攻击。<​​/ p>