为什么我无法使用NodeJS解密我用openssl加密的文件?

5

我在命令行中使用以下命令加密了一个文件:

openssl aes-256-cbc -in /tmp/text.txt -out /tmp/text.crypt

我尝试使用以下JavaScript代码对其进行解密:
crypto        = require( 'crypto' );
cipher_name   = 'aes-256-cbc';
password      = '*';
decoder       = crypto.createDecipher( cipher_name, password );
text_crypt    = njs_fs.readFileSync( '/tmp/text.crypt' );
chunks        = [];
chunks.push decoder.update( text_crypt, 'binary' );
chunks.push decoder.final( 'binary' );
text          = chunks.join( '' ).toString( 'utf-8' );

这个操作失败了

TypeError: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

我做错了什么?


类似的问题在这里得到了解答;使用openssl-nosalt选项可以解决这个问题。不过我不知道Node是否已经支持盐了。 - robertklep
搞定了!如果你在意的话,请将你的评论转换成答案,这样我就可以接受它。 - flow
我只找到了一个相关的问题 ;) 不过可以看看@AlexAtNet的答案! - robertklep
2
@robertklep - 顺便说一句,OpenSSL文档明确指出nosalt“除了测试目的或与古老版本的OpenSSL和SSLeay兼容性之外,不应使用”-http://www.openssl.org/docs/apps/enc.html - Alex Netkachov
@AlexAtNet 这就是为什么我没有回答,而只是对一个相关问题发表了评论。 - robertklep
1个回答

13

密码学很有趣。这里是解密带有盐的openssl加密文件的代码。

var crypto = require('crypto');

function md5(data) {
    var hash = crypto.createHash('md5');
    hash.update(data);
    return new Buffer(hash.digest('hex'), 'hex');
}

var text = require('fs').readFileSync('text.crypt');
var salt = text.slice(8, 16);
var cryptotext = text.slice(16);
var password = new Buffer('*');

var hash0 = new Buffer('');
var hash1 = md5(Buffer.concat([ hash0, password, salt ]));
var hash2 = md5(Buffer.concat([ hash1, password, salt ]));
var hash3 = md5(Buffer.concat([ hash2, password, salt ]));
var key = Buffer.concat([ hash1, hash2 ]);
var iv = hash3;

var decoder = crypto.createDecipheriv('aes-256-cbc', key, iv);

var chunks = [];
chunks.push(decoder.update(cryptotext, "binary", "utf8"));
chunks.push(decoder.final("utf8"));
console.log(chunks.join(''));

更新:有关CBC模式以及OpenSSL如何工作的更多细节。

如果您查看密码块链接模式中的流密码的工作方式,您会注意到加密数据需要两个初始值:初始化向量(IV)和密钥。重要的是,初始化向量的大小应该等于块大小,而密钥大小取决于算法,对于AES-256,它的长度为256位。

但是用户不希望设置256位的随机密码来访问其数据。这引发了一个问题,即如何从用户输入构造密钥和IV。OpenSSL通过将EVP_BytesToKey函数应用于用户输入来解决此问题,实际上是将MD5应用于密码和盐多次。

您可以通过执行以下命令来查看派生出的值:

C:\Tools\wget>openssl enc -aes-256-cbc -P
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:
salt=A94B7976B2534923
key=C8B806C86E60ED664B9C369628D1A78260753580D78D09EAEC04EAC1535077C3
iv =7B6FB26EB62C34F04F254A0C4F4F502A

这里的“key”和“iv”参数是密码的输入参数,“salt”用于随机化密文,以便对于相同的数据不会生成相同的密文。

OpenSSL将数据保存在文件中,如下所示:

Saltet__;[salt][cipher-text]

因此,要解密它,应执行以下步骤:

  1. 跳过“Salted”前缀
  2. 读取输入的8个字节并保存为盐
  3. 从密码和盐构造密钥和向量
  4. 使用计算出的密钥和向量应用AES-256-CBC解密器来解密文件的其余部分

上面的代码执行这些步骤并解密文件。


当我说你的回答可能以相当含蓄的方式解决了问题,但至少提出了和解决问题同样多的新问题时,请原谅我。也就是说,我不知道 openssl 默认使用盐,而 NodeJS 却对盐毫不知情。我为你重复调用 md5 函数感到困扰,并想知道 iv 可能是什么缩写。 - flow
希望这次更新能够有所帮助,如果您还有其他问题,请告诉我。 - Alex Netkachov
非常感谢。总是惊讶地发现自己对这些事情知之甚少。 - flow
我没有看到你考虑到指定给 EVP_BytesToKey 的轮数。 - Michael
天哪,刚刚在这个openssl-crypto上花了一个早上。请注意,只有在新的openssl版本中,如果你使用-md md5标志进行加密,它才能正常工作。 - Daphoque

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