NodeJS AES-256-CBC加密后,Linux下OpenSSL解密出现前置乱码求助
AES-256-CBC加密后OpenSSL解密明文前出现乱码的解决方法
我在NodeJS中编写了AES-256-CBC加密代码,使用PBKDF2派生密钥,添加Salted__前缀生成Base64密文。将该密文放到Linux服务器执行OpenSSL解密命令时,虽能得到明文“its a secret!”,但明文前出现乱码,请问如何解决?
相关原始代码与输出
NodeJS加密代码
const crypto = require('crypto'); const passphrase = 'test'; const iterations = 100000; const keyLength = 32; const saltLength = 8; const ivLength = 16; let salt = crypto.randomBytes(saltLength); let key = crypto.pbkdf2Sync(passphrase, salt, iterations, keyLength, 'sha256'); let iv = crypto.randomBytes(ivLength); console.log("Salt size: ",salt.length + " bytes"); console.log("Key size: ",key.length + " bytes"); console.log("IV size: ",iv.length +" bytes"); const dataToEncrypt = 'its a secret!'; const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); let encryptedData = cipher.update(dataToEncrypt, 'utf8', 'base64'); encryptedData += cipher.final('base64'); const saltedPrefix = Buffer.from('Salted__'); const prefixedData = Buffer.concat([saltedPrefix, salt, iv, Buffer.from(encryptedData, 'base64')]); console.log("Result HEX: ", prefixedData.toString('hex')); const encrypted = prefixedData.toString('base64'); /* FINAL SIZE */ console.log("Result size: ", encrypted.length +" bytes"); /* RESULT LOG */ console.log('Base64 encrypted: ' + encrypted);
NodeJS输出
Salt size: 8 bytes Key size: 32 bytes IV size: 16 bytes Result HEX: 53616c7465645f5f2d920c9fc07a1131ff26f94a8e2b8110691e022c824f9251256f28e1533a4dfcb7eda2ae5f665809 Result size: 64 bytes Base64 encrypted: U2FsdGVkX18tkgyfwHoRMf8m+UqOK4EQaR4CLIJPklElbyjhUzpN/Lftoq5fZlgJ
Linux解密命令
echo "U2FsdGVkX18tkgyfwHoRMf8m+UqOK4EQaR4CLIJPklElbyjhUzpN/Lftoq5fZlgJ" | (openssl enc -AES-256-cbc -d -a -pass "pass:test" -pbkdf2 -iter 100000 -md sha256 -p; echo) | tee >(hexdump -C)
解密输出
salt=2D920C9FC07A1131 key=C4B6F8A9A1DA258846E066D8B3D4A7028922376D87DC898255C8DE64DD994E09 iv =F7369730119C2461AB0A6CA1B8D3015D ▒▒f/B>▒=O▒▒琰its a secret! 00000000 73 61 6c 74 3d 32 44 39 32 30 43 39 46 43 30 37 |salt=2D920C9FC07| 00000010 41 31 31 33 31 0a 6b 65 79 3d 43 34 42 36 46 38 |A1131.key=C4B6F8| 00000020 41 39 41 31 44 41 32 35 38 38 34 36 45 30 36 36 |A9A1DA258846E066| 00000030 44 38 42 33 44 34 41 37 30 32 38 39 32 32 33 37 |D8B3D4A702892237| 00000040 36 44 38 37 44 43 38 39 38 32 35 35 43 38 44 45 |6D87DC898255C8DE| 00000050 36 34 44 44 39 39 34 45 30 39 0a 69 76 20 3d 46 |64DD994E09.iv =F| 00000060 37 33 36 39 37 33 30 31 31 39 43 32 34 36 31 41 |7369730119C2461A| 00000070 42 30 41 36 43 41 31 42 38 44 33 30 31 35 44 0a |B0A6CA1B8D3015D.| 00000080 f6 db 66 2f 01 42 3e 80 3d 4f 9d c5 04 e7 90 b0 |..f/.B>.=O......| 00000090 69 74 73 20 61 20 73 65 63 72 65 74 21 0a |its a secret!.| 0000009e
问题原因
你在NodeJS代码里错误地将IV直接追加到了Salted__前缀和salt之后,但OpenSSL的enc命令在使用PBKDF2时,会自动从密钥派生过程中生成IV(对于AES-256-CBC,PBKDF2会生成32字节密钥+16字节IV,共48字节的派生数据)。你额外添加的IV会被OpenSSL当作密文的一部分解密,从而在明文开头产生乱码。
OpenSSL的标准加密格式是:Salted__ + 8字节salt + 密文,不需要额外存储IV——IV是和密钥一起从PBKDF2派生出来的。
解决方案
修复后的NodeJS加密代码
修改代码,移除对IV的手动存储,改为从PBKDF2派生数据中提取IV,只保留Salted__前缀、salt和密文:
const crypto = require('crypto'); const passphrase = 'test'; const iterations = 100000; const keyLength = 32; const saltLength = 8; let salt = crypto.randomBytes(saltLength); // PBKDF2派生48字节数据:32字节密钥 + 16字节IV let keyIv = crypto.pbkdf2Sync(passphrase, salt, iterations, keyLength + 16, 'sha256'); let key = keyIv.slice(0, keyLength); let iv = keyIv.slice(keyLength); // 从派生数据中提取IV console.log("Salt size: ",salt.length + " bytes"); console.log("Key size: ",key.length + " bytes"); console.log("IV size: ",iv.length +" bytes"); const dataToEncrypt = 'its a secret!'; const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); let encryptedData = cipher.update(dataToEncrypt, 'utf8', 'base64'); encryptedData += cipher.final('base64'); const saltedPrefix = Buffer.from('Salted__'); // 只拼接前缀、salt和密文,不再加入IV const prefixedData = Buffer.concat([saltedPrefix, salt, Buffer.from(encryptedData, 'base64')]); console.log("Result HEX: ", prefixedData.toString('hex')); const encrypted = prefixedData.toString('base64'); console.log("Result size: ", encrypted.length +" bytes"); console.log('Base64 encrypted: ' + encrypted);
验证解密结果
用原OpenSSL命令解密修复后的密文,就能得到无乱码的明文:
echo "修复后的Base64密文" | (openssl enc -AES-256-cbc -d -a -pass "pass:test" -pbkdf2 -iter 100000 -md sha256 -p; echo) | tee >(hexdump -C)
额外说明
- OpenSSL的
enc工具在使用PBKDF2时,默认会从passphrase和salt派生密钥长度 + IV长度的字节数,不需要手动传递IV参数,也不需要在密文中存储IV。 - 若非要手动指定IV(自定义格式),需在OpenSSL解密时加上
-iv参数传入对应IV值,但这不符合OpenSSL标准加密格式,不推荐。
内容的提问来源于stack exchange,提问作者Daniel Morais
相关产品推荐
相关产品推荐

