Search code examples
javascriptnode.jsaescryptojsnode-crypto

Convert Node crypto aes-256-cbc to CryptoJS


How to convert the following Node's built-in crypto module encryption to CryptoJS?

const crypto = require('crypto');

const pass = 'some,password:)with>spec(chars*'
const cipher1 = crypto.createCipher('aes-256-cbc', pass)
const c1 = cipher1.update(input, 'utf8', 'hex') + cipher1.final('hex')

I tried something like this, but the results are not the same:

const CryptoJS = require('crypto-js');

const pass = 'some,password:)with>spec(chars*'
const cipher2 = CryptoJS.AES.encrypt(input, pass, {
    mode: CryptoJS.mode.CBC,
});
const c2 = cipher2.ciphertext.toString(CryptoJS.enc.Hex);

I need this to use as a Postman prerequest script as it does not support Node's crypto, but crypto-js.


Solution

  • Both codes use the OpenSSL proprietary key derivation function EVP_BytesToKey() with an iteration count of 1 and MD5 as digest.
    NodeJS does not use a salt, while CryptoJS applies a random salt. For this reason, the NodeJS result is unchanged for each encryption, while the CryptoJS result always changes (assuming the same plaintext and passphrase).

    Thus, to get the result of the NodeJS code with the CryptoJS code, you must not use a salt. However, by default, a salt is always applied. This can only be circumvented by explicitly determining key and IV with the key derivation function EvpKDF and then using both in the encryption:

    var input = "The quick brown fox jumps over the lazy dog";
    var pass = 'some,password:)with>spec(chars*'
    
    var keySize = 32/4;
    var ivSize = 16/4;
    var kdf = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize, hasher: CryptoJS.algo.MD5 }).compute(pass, ''); // no salt!
    var key = CryptoJS.lib.WordArray.create(kdf.words.slice(0, keySize), keySize * 4);
    var iv = CryptoJS.lib.WordArray.create(kdf.words.slice(keySize), ivSize * 4);
    var ciphertextCP = CryptoJS.AES.encrypt(input, key, {iv: iv}); // default: CBC, PKCS#7 padding
    var ciphertext = ciphertextCP.ciphertext.toString(CryptoJS.enc.Hex);
    document.getElementById("ct").innerHTML = ciphertext; // d98cf2d285bf0c1d796226190bf54d9c5540300ee1c6f35618f8bb3564b5053920ec958d31b41bbe4e4880e23543d709
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
    <p style="font-family:'Courier New', monospace;" id="ct"></p>

    This CryptoJS code produces the same ciphertext for the same plaintext and passphrase as the NodeJS code.


    Note that the key derivation with EVP_BytesToKey() and the chosen parameters is deprecated and considered insecure today. This is true for NodeJS to a greater extent than for CryptoJS, due to the lack of a salt.

    It is more secure to avoid the built-in key derivation function and specify the key directly.
    To do this, use createCipheriv() in NodeJS and pass the key as WordArray in the CryptoJS code. This way, a random IV must be explicitly generated for each encryption.
    Optionally, a reliable key derivation function like PBKDF2 can be used, which is supported by both libraries.