Search code examples
javascriptnode.jscryptographyaescryptojs

Problems when using AES crypto between Node and CryptoJS in browser


I want encrypt a string with Node, and decrypt the string with CryptoJS in browser.

Encrypt:

var crypto = require('crypto');

function encrypt(txt, cryptkey) {
    var cipher = crypto.createCipher('aes-256-cbc', cryptkey);
    var crypted = cipher.update(txt, 'utf8', 'hex');
    crypted += cipher.final('hex');
    return crypted;
}

encrypt('1', 'key'); // 83684beb6c8cf063caf45cb7fad04a50

Include:

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>

Decrypt:

var decrypted = CryptoJS.AES.decrypt('83684beb6c8cf063caf45cb7fad04a50', 'key');
console.log(decrypted.toString(CryptoJS.enc.Utf8)); // empty string

The actual result is empty string.

What is the right way to decrypt data from node?


Solution

  • CryptoJS supports the same password-based encryption mode that the crypto module in node.js supports which is implemented as the equivalent to EVP_BytesToKey. CryptoJS generates a random salt by default, but node.js doesn't and uses an empty salt. An empty salt is bad and should not be used. Also, it's not secure to derive a key from a password with this method. One needs to use PBKDF2 (supported by CryptoJS and node.js) or similar with a lot of iterations and a random salt.

    var ctHex = '83684beb6c8cf063caf45cb7fad04a50';
    var ct = CryptoJS.enc.Hex.parse(ctHex);
    var salt = CryptoJS.lib.WordArray.create(0); // empty array
    var decrypted = CryptoJS.AES.decrypt({ciphertext: ct, salt: salt}, 'key');
    
    document.querySelector("#dec").innerHTML = decrypted.toString(CryptoJS.enc.Utf8);
    <script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
    Expected: "1"<br/>Got: "<span id="dec"></span>"


    You said that this will be done over HTTP.

    If you want to use transport security without user intervention, then this is completely insecure, because the key needs to be transmitted alongside of the ciphertext which makes this at best obfuscation.

    If the user and the server both know the password before communication, then this is still insufficient, because the key derivation that both CryptoJS and node.js provide is insufficient and something like PBKDF2 must be used. MD5 is easily brute-forceable.

    You would need to use asymmetric cryptography to protect this communication against a passive attacker (one that cannot inject arbitrary packets into the stream between server and client). I suggest that you generate an RSA key pair and sent the public key to the client so that the client can encrypt a message to the server. You can use forge for that.


    Encryption would look like this:

    var salt = CryptoJS.lib.WordArray.create(0); // empty array
    var params = CryptoJS.kdf.OpenSSL.execute('key', 256/32, 128/32, salt);
    var pt = '1';
    var encrypted = CryptoJS.AES.encrypt(pt, params.key, {iv: params.iv});
    
    document.querySelector("#enc").innerHTML = encrypted.ciphertext.toString();
    <script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
    Expected: "83684beb6c8cf063caf45cb7fad04a50"<br/>Got: "<span id="enc"></span>"