用 '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
我试过:
- 将 decrypt 中的加密算法更改为 192 或其他(但 'crypto-js' 文档说默认为“256”是使用密码
- 客户端base64编码。也尝试了十六进制编码
例如:
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)
- 我正在使用 'crypto.createDecipher' 而不是 'crypto.createDecipheriv' 因为我在这个项目上使用的是 Node v8.12.0
我想就是这样...我感谢任何帮助或提示!
如果密钥作为字符串传递,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
我正在使用 '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
我试过:
- 将 decrypt 中的加密算法更改为 192 或其他(但 'crypto-js' 文档说默认为“256”是使用密码
- 客户端base64编码。也尝试了十六进制编码
例如:
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)
- 我正在使用 'crypto.createDecipher' 而不是 'crypto.createDecipheriv' 因为我在这个项目上使用的是 Node v8.12.0
我想就是这样...我感谢任何帮助或提示!
如果密钥作为字符串传递,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