We are trying to match the crypto and crypto-js output. Our requirement is to encrypt string in browser but crypto will not support browser side encryption. So, we are trying to match the output using crypto-js. Each time crypto-js produces different output.
const crypto = require('crypto');
const CryptoJS = require('crypto-js');
const payload = {
name: 'John Doe'
}
// Node Package
function encryptNode(text) {
const cipher = crypto.createCipher('aes-256-cbc', 'devOps');
return cipher.update(Buffer.from(JSON.stringify(text)), 'utf8', 'hex') + cipher.final('hex');
}
console.log(encryptNode(payload));
// Browser Package
function encryptBrowser(text) {
const ciphertext = CryptoJS.AES.encrypt(JSON.stringify(text), 'devOps', { mode: CryptoJS.mode.CBC });
return ciphertext.toString(CryptoJS.format.Hex);
}
console.log(encryptBrowser(payload));
Output:
crypto(Expected): dfe03c7e825e9943aa6ec61deb4a8a73fdba0016a13c59c628ce025f39d44c7c
crypto-js: 4e5453abe7bd53d67d88aa4f040356c649fe0101366d05ce4c7d625cfd052cdc
crypto.createCipher
and CryptoJS.AES.encrypt
utilize the functionality of the OpenSSL-function EVP_BytesToKey
, which expects a password, an optional 8-byte salt, a digest and an iteration count, and generates a key and an IV from these data. Both functions use the MD5 digest and an iteration count of 1 as fixed parameters.
crypto.createCipher
doesn't use a salt, so that the same key and IV are generated each time and thus the same ciphertext (assuming the same plaintext).
In contrast, CryptoJS.AES.encrypt
generates a random salt each time (here and here), so that a different key and IV are generated each time, and thus a different ciphertext (even for an identical plaintext). For decryption (beside the password) the salt is needed, which isn't secret and can be passed together with the ciphertext (e.g. in a CipherParams
-object to CryptoJS.AES.decrypt
).
The posted code snippet therefore behaves as expected: The ciphertext created with crypto.createCipher
doesn't change, the ciphertext created with CryptoJS.AES.encrypt
changes each time.
EVP_BytesToKey
is weak and shouldn't be used for security reasons (at least when using MD5 and an iteration count of 1). The same applies to crypto.createCipher
(even more so because of the missing salt), which is deprecated anyway, and that overloaded variant of CryptoJS.AES.encrypt
, which uses EVP_BytesToKey
. Both libraries provide additional or overloaded methods to which a key and IV can be passed directly (which can be derived from a passphrase beforehand using a secure procedure if required).