So I have a piece of code which encrypts a string with a passphrase. It uses the CryptoJS AES encrypt function (CryptoJS.AES.encrypt
) and looks like this...
CryptoJS.AES.encrypt(data, password).toString();
Going forward, I don't want to be using CryptoJS, as it's officially deprecated/not maintained, and I would instead like to use Forge.js. I've attempted to read through the Forge.js docs on GitHub to find a solution, but haven't been able to find anything which uses passphrases instead of manually creating the key & IV.
I've taken a look at the CryptoJS archive at https://code.google.com/archive/p/crypto-js/ and it seems that if the encrypt
function is passed a string as the second argument (key) it's used as a passphrase to derive a key and IV. But it doesn't detail how it does this.
It seems that base64 decoding the result gives a string that starts with Salted__
then a comma and then the encrypted blob of binary text, and I'm unsure even how I would pass the "salt" through to Forge.
How would I go about decrypting this blob of data using Forge.js only?
CryptoJS supports OpenSSL's EVP_BytesToKey function, which derives a key and IV from a freshly generated salt and password with one round of MD5. There is an example on the forge documentation page:
Using forge in node.js to match openssl's "enc" command line tool (Note: OpenSSL "enc" uses a non-standard file format with a custom key derivation function and a fixed iteration count of 1, which some consider less secure than alternatives such as OpenPGP/GnuPG):
var forge = require('node-forge'); var fs = require('fs'); // openssl enc -des3 -in input.txt -out input.enc function encrypt(password) { var input = fs.readFileSync('input.txt', {encoding: 'binary'}); // 3DES key and IV sizes var keySize = 24; var ivSize = 8; // get derived bytes // Notes: // 1. If using an alternative hash (eg: "-md sha1") pass // "forge.md.sha1.create()" as the final parameter. // 2. If using "-nosalt", set salt to null. var salt = forge.random.getBytesSync(8); // var md = forge.md.sha1.create(); // "-md sha1" var derivedBytes = forge.pbe.opensslDeriveBytes( password, salt, keySize + ivSize/*, md*/); var buffer = forge.util.createBuffer(derivedBytes); var key = buffer.getBytes(keySize); var iv = buffer.getBytes(ivSize); var cipher = forge.cipher.createCipher('3DES-CBC', key); cipher.start({iv: iv}); cipher.update(forge.util.createBuffer(input, 'binary')); cipher.finish(); var output = forge.util.createBuffer(); // if using a salt, prepend this to the output: if(salt !== null) { output.putBytes('Salted__'); // (add to match openssl tool output) output.putBytes(salt); } output.putBuffer(cipher.output); fs.writeFileSync('input.enc', output.getBytes(), {encoding: 'binary'}); } // openssl enc -d -des3 -in input.enc -out input.dec.txt function decrypt(password) { var input = fs.readFileSync('input.enc', {encoding: 'binary'}); // parse salt from input input = forge.util.createBuffer(input, 'binary'); // skip "Salted__" (if known to be present) input.getBytes('Salted__'.length); // read 8-byte salt var salt = input.getBytes(8); // Note: if using "-nosalt", skip above parsing and use // var salt = null; // 3DES key and IV sizes var keySize = 24; var ivSize = 8; var derivedBytes = forge.pbe.opensslDeriveBytes( password, salt, keySize + ivSize); var buffer = forge.util.createBuffer(derivedBytes); var key = buffer.getBytes(keySize); var iv = buffer.getBytes(ivSize); var decipher = forge.cipher.createDecipher('3DES-CBC', key); decipher.start({iv: iv}); decipher.update(input); var result = decipher.finish(); // check 'result' for true/false fs.writeFileSync( 'input.dec.txt', decipher.output.getBytes(), {encoding: 'binary'}); }
This example is shown for Triple DES, but it works in the same way for AES. You just have to change the ivSize
to 16.