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

Man*_*rio 2 encryption cryptography aes node.js

我正在使用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
Run Code Online (Sandbox Code Playgroud)

我认为问题在于加密或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");
});
Run Code Online (Sandbox Code Playgroud)

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

概括:

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

Art*_* B. 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");
Run Code Online (Sandbox Code Playgroud)

其他考虑:

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

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

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