用 'crypto-js' 编码并用 'crypto' 解码(节点)

Encode with 'crypto-js' and decode with 'crypto' (Node)

我正在使用 'cypto-js' 在浏览器中使用高级加密标准 (AES) 加密字符串,并需要使用节点 'crypto'.

在服务器上对其进行解密

我可以单独使用 'crypto-js' 加密/解密,但是当我尝试使用 'crypto.createDecipher' 使用 'crypto' (节点)解密时,我收到错误消息 'bad decrypt' 或 'wrong block size' 取决于我的尝试。

例如:仅使用 'crypto-js' - 工作正常

crypto-js

const cypherParams = CryptoJS.AES.encrypt('my message', 'passphrase')
const decrypted = CryptoJS.AES.decrypt(cypherParams, 'passphrase')
console.log(decrypted.toString(CryptoJS.enc.Utf8)) // 'my message' - works!

ex:用 'crypto-js' 编码,用 'crypto' 解码 - 导致错误

[client]
const cypherParams = CryptoJS.AES.encrypt('my message', 'passphrase')

[server]
const decipher = crypto.createDecipher('aes-256-cbc', 'passphrase');
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8'); 
// results in 'bad decrypt' or 'block size' error in console


console.log(decrypted); // this never executes

我试过:

例如:

const cypherParams = CryptoJS.AES.encrypt('my message', 'passphrase')
const base64Encoded = cipherParams.toString(CryptoJS.enc.Base64)

and

const cypherParams = CryptoJS.AES.encrypt('my message', 'passphrase')
const cypherParams.ciphertext = cipherParams.toString(CryptoJS.enc.Base64)

我想就是这样...我感谢任何帮助或提示!

如果密钥作为字符串传递,CryptoJS.AES.encrypt() 使用 OpenSSL 密钥派生函数 (KDF) EVP_BytesToKey() to derive a 32 bytes key (and a 16 bytes IV), i.e. indeed AES-256 is applied for encryption (here and here)。在此过程中会生成一个随机的 8 字节盐,这确保每次都会产生 不同的 key/IV 对结果。
NodeJS 方法 crypto.createCipher() uses the same KDF, but does not apply a salt, so that always the same key/IV pair is generated. Therefore crypto.createDecipher() 也不考虑盐。
总而言之,这意味着用CryptoJS.AES.encrypt()加密时生成的密钥对与用crypto.createDecipher()解密时生成的密钥对不同,解密失败。

据我所知,这两种方法都没有提供控制是否使用盐的可能性,因此无法消除不兼容问题。

因此,一个解决方案是省略内置 KDF(无论如何都被认为是弱的,这反过来也是 crypto.createCipher()/crypto.createDecipher() 被弃用的原因)并使用可靠的 KDF,例如PBKDF2 并使用从它派生的 key/IV 对。
在 CryptoJS 端,您必须将密钥和 IV 作为 WordArray 传递,在 NodeJS 端,您必须使用 create.createDecipheriv().
加解密之间的联系就是在推导密钥时随机生成salt。盐不是秘密的,通常与密文连接并以这种方式传递给接收者。

您提到您使用的版本是 Node v8.12.0,因此您无法申请 crypto.createDecipheriv()。但是 crypto.createDecipheriv() 从 v0.1.94 开始可用,因此它应该在您的环境中可用。


客户端加密的示例实现 (CryptoJS):

// Generate random salt
var salt16 = CryptoJS.lib.WordArray.random(16);                                     // Random 16 bytes salt

// Derive key and IV via PBKDF2
var keyIV = CryptoJS.PBKDF2("My Passphrase", salt16, {
  keySize: (32 + 16) / 4,                                                           // 12 words a 4 bytes = 48 bytes
  iterations: 1000,                                                                 // Choose a sufficiently high iteration count
  hasher: CryptoJS.algo.SHA256                                                      // Default digest is SHA-1       
}); 
var key32 = CryptoJS.lib.WordArray.create(keyIV.words.slice(0, 32 / 4));            // 8 words a 4 bytes = 32 bytes 
var iv16 = CryptoJS.lib.WordArray.create(keyIV.words.slice(32 / 4, (32 + 16) / 4)); // 4 words a 4 bytes = 16 bytes 

// Encrypt
var message = 'The quick brown fox jumps over the lazy dog';
var cipherParams = CryptoJS.AES.encrypt(message, key32, {iv:iv16});
var ciphertext = cipherParams.ciphertext;

// Concatenate salt and ciphertext
var saltCiphertext = salt16.clone().concat(ciphertext);
var saltCiphertextB64 = saltCiphertext.toString(CryptoJS.enc.Base64);               // This is passed to the recipient    

// Outputs
console.log("Salt:\n", salt16.toString(CryptoJS.enc.Base64).replace(/(.{56})/g,'\n'));
console.log("Ciphertext:\n", ciphertext.toString(CryptoJS.enc.Base64).replace(/(.{56})/g,'\n'));
console.log("Salt | Ciphertext:\n", saltCiphertextB64.replace(/(.{56})/g,'\n'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

服务器端(NodeJS)解密的示例实现:

var crypto = require('crypto');

// Separate salt and ciphertext
var saltCiphertextB64 = 'lhBp/LKhv8TxeJYnLDy/F2oaQYScOVFVLLZxd00HiRy9fYy97lX2ZjGJt+S4x+GF9X0AEjAS9k8tUDHKCz4srQ==';  // Received from client
var saltCiphertextBuf = Buffer.from(saltCiphertextB64, 'base64');
var saltBuf = saltCiphertextBuf.slice(0,16);
var ciphertextBuf = saltCiphertextBuf.slice(16);

// Derive key and IV via PBKDF2
var keyIVBuf = crypto.pbkdf2Sync("My Passphrase", saltBuf, 1000, 32 + 16, 'sha256');
var keyBuf = keyIVBuf.slice(0, 32); 
var ivBuf = keyIVBuf.slice(32, 32 + 16);

// Decrypt
var decipher = crypto.createDecipheriv("aes-256-cbc", keyBuf, ivBuf);
var plaintextBuf = Buffer.concat([decipher.update(ciphertextBuf), decipher.final()]);

// Outputs
console.log("Plaintext: ", plaintextBuf.toString('utf8')); // Plaintext:  The quick brown fox jumps over the lazy dog