EVP_DecryptFinal_ex: 在使用Node.js时出现解密错误

16

使用以下的Node.js:

var crypto = require('crypto');
var encrypt = function (input, password, callback) {
    var m = crypto.createHash('md5');
    m.update(password);
    var key = m.digest('hex');

    m = crypto.createHash('md5');
    m.update(password + key);
    var iv = m.digest('hex');
    console.log(iv);

    var data = new Buffer(input, 'utf8').toString('binary');

    var cipher = crypto.createCipheriv('aes-256-cbc', key, iv.slice(0,16));
    var encrypted = cipher.update(data, 'binary') + cipher.final('binary');
    var encoded = new Buffer(encrypted, 'binary').toString('base64');
    callback(encoded);
};

var decrypt = function (input, password, callback) {
    // Convert urlsafe base64 to normal base64
    input = input.replace(/\-/g, '+').replace(/_/g, '/');
    // Convert from base64 to binary string
    var edata = new Buffer(input, 'base64').toString('binary');

    // Create key from password
    var m = crypto.createHash('md5');
    m.update(password);
    var key = m.digest('hex');

    // Create iv from password and key
    m = crypto.createHash('md5');
    m.update(password + key);
    var iv = m.digest('hex');

    // Decipher encrypted data
    var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv.slice(0,16));
    var decrypted = decipher.update(edata, 'binary') + decipher.final('binary');
    var plaintext = new Buffer(decrypted, 'binary').toString('utf8');

    callback(plaintext);
};

我执行了以下命令:

encrypt("uWeShxRrCKyK4pcs", "secret", function (encoded) {
    console.log(encoded);
    decrypt(encoded, "secret", function (output) {
        console.log(output);
    });
});

加密似乎运行良好,但是当我尝试解密时,我收到以下错误:

错误:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt at Error (native) at Decipheriv.Cipher.final (crypto.js:202:26)

我对密码学还不太了解,所以不知道为什么会出现这个错误。我现在只需要修复它。


1
  1. MD5不应该被使用,因为它既不安全,也不足以保证密码的简单哈希安全。
  2. 使用随机盐迭代HMAC约100毫秒(盐需要与哈希值一起保存)。使用password_hash、PBKDF2、Bcrypt或类似的函数。重点是让攻击者花费大量时间通过暴力破解来查找密码。
请参阅OWASP(开放Web应用程序安全项目)密码存储备忘单
- zaph
谢谢,但我不知道如何更改我的代码来实现那个。我会尝试看一下bcrypt。 - Sonu Kapoor
是时候进行调试了。在加密和解密过程中,将密钥和iv以十六进制转储,并验证它们是否正确且长度正确。AES密钥应为32字节,iv为16字节,完全一致。在加密后立即转储加密数据,并在解密之前立即再次转储,验证它们是否相同。 - zaph
如果这是一个有很多用户的严肃应用程序,您需要请领域专家至少审查代码以及加密的使用方式。即使对于经验丰富的开发人员来说,加密安全性也很难正确实现。 - zaph
1
一切似乎都是正确的。我注意到,如果我使用比16位更短的密码,那么一切都可以正常工作。这似乎与此问题有关:https://dev59.com/I5Dea4cB1Zd3GeqPhd7J?rq=1 然而,我的代码已经包含了答案中建议的更改。 - Sonu Kapoor
4个回答

11
你混淆了两种不同的编码方式。请参考 cipher.update(data[, input_encoding][, output_encoding])cipher.final([output_encoding]),然后再看下面的内容。
var encrypted = cipher.update(data, 'binary') + cipher.final('binary');

这里方括号表示一个可选的函数输入。如果您传递比所需更多的值,则额外的值将从左侧匹配到可选输入。

应该是这样的

var encrypted = cipher.update(data, 'binary', 'binary') + cipher.final('binary');
问题在于输出的是一个缓冲区,它会自动转换为十六进制编码的字符串,而不是一个"binary"字符串。 无论如何,这段代码存在很多问题,你应该从头开始使用一个高度规范化的现有库。
  • 你必须有一个随机IV,将其前置到密文中以达到语义安全。

  • 密码熵值低,不能用作密钥。一个MD5调用并不能改变这个事实。应使用已知方案PBKDF2、bcrypt、scrypt或Argon2进行密钥派生(增加安全性),设置高迭代计数/成本因子。别忘了盐。

  • 使用加密后MAC方案的消息认证码(如HMAC-SHA256)对密文进行身份验证。否则,攻击者可能会操纵密文,并且您甚至无法检测到更改。这是失去数据的第一步,也容易受到填充区域攻击。


5
由于这个问题是在谷歌上第一个出现的,这里提供另一个解决方案。 如果链接失效,可以更新解密函数来进行修复。 lifesaver bad decrypt
function decrypt(text) {
    let iv = Buffer.from((text.split(':')[1]).split('=')[0], 'hex')//will return iv;
    let enKey = Buffer.from(text.split('=')[1], 'hex')//will return key;
    let encryptedText = Buffer.from(text.split(':')[0], 'hex');//returns encrypted Data
    let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(enKey), iv);
// Added this line here
    decipher.setAutoPadding(false);
    let decrypted = decipher.update(encryptedText);
    decrypted = Buffer.concat([decrypted, decipher.final()]);
    return decrypted.toString();
    //returns decryptedData
}

1
虽然它消除了错误,但并没有解决根本原因。现在我从decrypted.toString()中没有得到预期的结果。 - m.spyratos

0
一个最近的工作示例。 要小心保持密钥相同。我的错误是在每次调用时生成密钥。
var express = require("express");
var router = express.Router();
var crypto = require("crypto");
/* GET users listing. */

const algorithm = "aes-256-cbc";
const key = "loC3dzXsvgCips3Q6jHa6hjmeaUr4Eak";

router.post("/encrypt", function (req, res, next) {
  var mystr = encrypt(`${req.body.message}`);
  res.json({ encrypted_message: mystr });
});

router.post("/decrypt", function (req, res, next) {
  var mystr = req.body;
  let data = decrypt(mystr);
  res.json({ plan_text: data });
});
module.exports = router;

function encrypt(text) {
  const iv = crypto.randomBytes(16);
  let cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv);
  let encrypted = cipher.update(text);
  encrypted = Buffer.concat([encrypted, cipher.final()]);
  return { iv: iv.toString("hex"), encryptedData: encrypted.toString("hex") };
}

function decrypt(text) {
  let iv = Buffer.from(text.iv, "hex");
  let encryptedText = Buffer.from(text.encryptedData, "hex");
  let decipher = crypto.createDecipheriv(algorithm, Buffer.from(key), iv);
  let decrypted = decipher.update(encryptedText);
  decrypted = Buffer.concat([decrypted, decipher.final()]);
  return decrypted.toString();
}

-3
我找到了原因,是因为在加密和解密过程中使用了不同的密钥或向量。我们必须使用相同的密钥和向量用于解密内容。唯一的解决方法是将iv&key保存在用于加密数据的数组中或者使用分隔符将iv&key与加密数据连接起来。
例子一:
function encrypt(text) {
    let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
    let encrypted = cipher.update(text);
    encrypted = Buffer.concat([encrypted, cipher.final()]);
    return encrypted.toString('hex') + ':' + iv.toString('hex') + '=' + 
    key.toString('hex');
    //returns encryptedData:iv=key
}

function decrypt(text) {
    let iv = Buffer.from((text.split(':')[1]).split('=')[0], 'hex')//will return iv;
    let enKey = Buffer.from(text.split('=')[1], 'hex')//will return key;
    let encryptedText = Buffer.from(text.split(':')[0], 'hex');//returns encrypted Data
    let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(enKey), iv);
    let decrypted = decipher.update(encryptedText);
    decrypted = Buffer.concat([decrypted, decipher.final()]);
    return decrypted.toString();
    //returns decryptedData
}

例子二:

function encrypt(text) {
    let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
    let encrypted = cipher.update(text);
    encrypted = Buffer.concat([encrypted, cipher.final()]);
    return { 
        "encryptedData": encrypted.toString('hex'),
        "iv" : iv.toString('hex'),
        "key" : key.toString('hex');
    //returns an Array of key, iv & encryptedData
  }
}

function decrypt(text) {
    let iv = Buffer.from((text.iv, 'hex')//will return iv;
    let enKey = Buffer.from(text.key, 'hex')//will return key;
    let encryptedText = Buffer.from(text.encryptedData, 'hex');//returns encrypted Data
    let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(enKey), iv);
    let decrypted = decipher.update(encryptedText);
    decrypted = Buffer.concat([decrypted, decipher.final()]);
    return decrypted.toString();
    //returns decryptedData
}

6
等等,你把密钥和密文一起存储?这并没有提供任何安全性,而只是简单的混淆,因为用于解密的密钥就在密文旁边。IV不应该是机密的,可以与密文一起存储。另一方面,密钥是唯一需要保密的东西。 - Artjom B.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接