Search code examples
node.jsencryptionopensslcryptojs

Decrypt openssl AES with CryptoJS


I'm trying to decrypt a file encrypted with openssl using CryptoJS 3.1.5.

Everything works fine if I encrypt and decrypt using CryptoJS, same goes for OpenSSL in shell, but when I try to mix CryptoJS with OpenSSL everything goes wrong.

The file is created using this command:

openssl enc -aes-256-cbc -in file.txt -out file.enc -k password

and I try to decrypt like this:

fs.readFile('file.enc', function(err, data) {
  var decrypted = CryptoJS.AES.decrypt(
                    data.toString(),
                    "password",
                    { mode : CryptoJS.mode.CBC }
                  );

  console.log(decrypted.toString(CryptoJS.enc.Utf8));
});

// Give me this err: Uncaught Error: Malformed UTF-8 data

And in the other way, I do :

fs.readFile('file.txt', function(err, data) {
  var encrypted = CryptoJS.AES.encrypt(
                    data.toString(),
                    "password",
                    { mode : CryptoJS.mode.CBC });

  fs.writeFile('file.enc', encrypted);
});

And then in Shell:

openssl enc -d -aes-256-cbc -in file.enc -out file2.txt -k password
// Give me this err: bad magic number

Am I missing something obvious ?


Solution

  • Not definitely an answer yet but too much for comments:

    Commandline openssl enc by default uses password-based encryption (PBE) with salt, which means the actual encryption key, and IV when applicable which it is for CBC, are computed from the given password and a random salt value by a Password Based Key Derivation Function that makes it more difficult for an adversary to try password-guessing attacks. I don't know your JS module (or much JS at all) but the webpage you link lists a variety of low-level primitives suggesting it does not automatically do PBE. A text string like "password" is (possibly) suitable for PBE, but not direct AES encryption where the key must be exactly 128, 192 or 256 bits and should be random binary data.

    If you want openssl's semi-standard PBE, match it on the JS side; the item evpkey sounds possibly helpful, since EVP is the openssl module involved and I know no other (PB)KDF scheme that would be called EVP. If not, the enc default PBE is just MD5 of the password concatenated with the salt, iterated with feedback as many times as needed which in this case is three. See https://superuser.com/questions/455463/openssl-hash-function-for-generating-aes-key for an example in (mostly) perl. OpenSSL prefixes the 8 ASCII chars "Salted__" and the 8 bytes of salt to the file, so you need to remove those (and use the salt) before decrypt, or add them after encrypt.

    If you want raw encryption, choose a more suitable key (on whichever side), and a unique and unpredictable IV unless you always use a new key in which case you can use a fixed IV, and on the openssl side use -K (note uppercase) and -iv to specify those values in hex. See the manpage on any Unix system with openssl installed or https://www.openssl.org/docs/manmaster/apps/enc.html .

    Plus in either case enc defaults to "PKCS#5" (really PKCS#7) padding. I don't know if your JS module does; if not you should specify it. Unless you can guarantee your plaintexts will always be an exact multiple of 16 bytes (after any encoding like UTF8); then you could specify (or maybe default) no padding on the JS side and specify -nopad on the openssl side.