David
David

Reputation: 14963

When encrypting, decrypting, then re-encrypting a string with node-forge's AES impl, why are the encrypted and re-encrypted strings different?

I am trying to use node-forge to decrypt strings encrypted by another application. After decrypting I am not getting the original strings back, so I decided to put together the following SSCCE that encrypts a string, decrypts it, then re-encrypts it. The results I get don't make sense.

Questions:

  1. First and foremost, what am I doing wrong? i.e. why is the decrypted hex different from the original hex, and why is the re-encrypted hex different from the encrypted hex?

  2. All of the code examples in the node-forge docs get the decrypted output as hex. What's up with this? I want plain text back i.e. 'hi'. How do I ask the library to give me text instead (calling decypher.output.toString() results in an error.)

  3. My ultimate goal is to be able to decrypt the output of: echo -n "hi" | openssl enc -aes-256-ctr -K $(echo -n redacted12345678 | openssl sha256) -iv 1111111111111111 -a -A -nosalt using a javascript library. Any advice on how to do that would be greatly appreciated.

SSCCE:

var forge = require('node-forge'); //npm install node-forge

//Inital data
var data = 'hi';
var iv = '1111111111111111';
var password = 'redacted12345678';

var md = forge.md.sha256.create();
md.update(password)
var keyHex = md.digest().toHex();
var key = Buffer.from(keyHex, 'hex').toString()

var cipher = forge.cipher.createCipher('AES-CTR', key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(data));
cipher.finish(); 
var encrypted = cipher.output.toHex()

console.log("encrypted: " + encrypted) //encrypted: 7457

var decipher = forge.cipher.createDecipher('AES-CTR', key)
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(encrypted));
decipher.finish(); 
var decrypted = decipher.output.toHex()

console.log("decrypted: " + decrypted) //decrypted: 2b0a684b

var recipher = forge.cipher.createCipher('AES-CTR', key);
recipher.start({iv: iv});
recipher.update(forge.util.createBuffer(decrypted));
recipher.finish(); 
var reencrypted = recipher.output.toHex()

console.log("reencrypted: " + reencrypted) //reencrypted: 2e5c6d1dc7cfa554

Upvotes: 1

Views: 2296

Answers (1)

Robby Cornelissen
Robby Cornelissen

Reputation: 97322

I've rewritten the OpenSSL command you're trying to mimic as follows:

echo -n "hi" | openssl enc -aes-256-ctr \
    -K $(echo -n redacted12345678 | openssl sha256 -binary | xxd -p -c 256) \
    -iv $(echo -n 1111111111111111 | xxd -p) -a -A -nosalt

The changes I made are due to the following:

  • The hex ouput that the openssl sha256 command generates, is prefixed with (stdin)= (at least on my distribution), so I just ran the binary hash of your password through xxd to get the key as a clean hex string.
  • The openssl enc command expects the IV to be in hex format, so I also ran that through xxd. (Since 16 bytes are required for the AES IV, your command would have resulted in an IV value of 111111111111111100000000000000, which I assume is not what you were aiming for.)

Executing this yields the following base64 output for the encrypted string:

JAA=

To replicate the same, I modified your JavaScript code as follows:

const forge = require('node-forge');

const data = 'hi', iv = '1111111111111111', password = 'redacted12345678';

const key = forge.md.sha256.create().update(password).digest().getBytes();

const cipher = forge.cipher.createCipher('AES-CTR', key);
cipher.start({ iv });
cipher.update(forge.util.createBuffer(data));
cipher.finish(); 
const encryptedBytes = cipher.output.getBytes();
const encryptedBase64 = forge.util.encode64(encryptedBytes);

console.log("encrypted: " + encryptedBase64);

const decipher = forge.cipher.createDecipher('AES-CTR', key)
decipher.start({ iv });
decipher.update(forge.util.createBuffer(encryptedBytes));
decipher.finish(); 
const decryptedBytes = decipher.output.getBytes();
const decryptedString = forge.util.encodeUtf8(decryptedBytes);

console.log("decrypted: " + decryptedString);

const recipher = forge.cipher.createCipher('AES-CTR', key);
recipher.start({ iv });
recipher.update(forge.util.createBuffer(decryptedBytes));
recipher.finish(); 
const reencryptedBytes = recipher.output.getBytes();
const reencryptedBase64 = forge.util.encode64(reencryptedBytes);

console.log("reencrypted: " + reencryptedBase64);

Which generates matching output:

encrypted: JAA=
decrypted: hi
reencrypted: JAA=

In essence, everything works correctly when the entire encryption/decryption operation is done using raw bytes, and only converting from/to hex, base64 or UTF-8 string when processing input or presenting output.

Upvotes: 1

Related Questions