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.
hi
(hex equivalent would be 6869)7457
2b0a684b
2e5c6d1dc7cfa554
Questions:
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?
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.)
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
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:
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.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.