Decrypt a node.js aes-256-cbc string in openssl - node.js

I have the following example code that generates an encrypted value in node.js using the aes-256-cbc algorithm, a fixed key, and an iv in node.js.
const crypto = require('crypto');
const iv = '0f9387b3f94bf06f';
const key = 'ZTk0YTU5YjNhMjk4NGI3NmIxNWExNzdi';
const encrypt = (value) => {
const cipher = crypto.createCipheriv('AES-256-CBC', key, iv);
let encrypted = cipher.update(value, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
};
console.log('Encrypted value: ', encrypt('HelloWorld'));
The encrypted value that is created by the above script, encrypted the string HelloWorld is the following hex value:
0c491f8c5256b9744550688fc54926e8
In bash and using openssl what script can I execute with the given encrypted value above to give me a decrypted value of HeloWorld?
I have spent lots of time struggling on this, using openssl, various options and accompanying commands but am struggling to generate the same output.
Any gurus out there who can help?

According to Node.js documentation, the function crypto.createCipher() doesn't use salt. You then need to explicitly say it to openssl.
Moreover the function you use doesn't expect IV. If you would require IV, then use crypto.createCipheriv().
So the openssl command is the following:
$ echo dd64232d380669d1a09378e1dc9de762 | xxd -ps -r | openssl enc -d -aes-256-cbc -k ZTk0YTU5YjNhMjk4NGI3NmIxNWExNzdi -nosalt
HelloWorld
Note that I'm using xxd to convert the string into bytes. You could use hexdump, od or other...
In response of the last OP's change, here the correct openssl command.
Obviously if you specify explicitly the IV, you don't need the salt and need to the key in bytes format (option -K)
echo 0c491f8c5256b9744550688fc54926e8 | xxd -ps -r | openssl enc -d -aes-256-cbc -iv 30663933383762336639346266303666 -K 5a546b3059545535596a4e684d6a6b344e4749334e6d49784e5745784e7a6469

Related

Trying to symmetrically encrypt a value for storage in the client (httpOnly cookie) and having an issue decrypting

I am trying to encrypt a value on my server with a private key to store it on the client within an httpOnly cookie.
I am having trouble with the encryption/decryption lifecycle
function encrypt(input) {
const encryptedData = crypto.privateEncrypt(
privateKey,
Buffer.from(input)
)
return encryptedData.toString('base64')
}
function decrypt(input) {
const decryptedData = crypto.privateDecrypt(
{ key: privateKey },
Buffer.from(input, 'base64'),
)
return decryptedData.toString()
}
const enc = encrypt('something moderately secret')
const dec = decrypt(enc)
console.log(dec) // 'something moderately secret'
However the crypto.privateDecrypt function is throwing with
Error: error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error
Side question, is it safe to reuse the same private key the server uses to sign JWTs. It's an rsa key generated using ssh-keygen -t rsa -b 4096 -m PEM -f RS256.key
So, you don't use crypto.privateEncrypt() with crypto.privateDecrypt(). That's not how they work. Those functions are for asymmetric encryption, not for symmetric encryption. You use either of these two pairs:
crypto.publicEncrypt() ==> crypto.privateDescrypt()
crypto.privateEncrypt() ==> crypto.publicDecrypt()
So, that's why you're getting the error you're getting. The nodejs doc for crypto.privateDecript() says this:
Decrypts buffer with privateKey. buffer was previously encrypted using the corresponding public key, for example using crypto.publicEncrypt().
If what you really want is symmetric encryption, there are a bunch of options in the crypto module for that. There are some examples shown here: https://www.section.io/engineering-education/data-encryption-and-decryption-in-node-js-using-crypto/ and https://fireship.io/lessons/node-crypto-examples/#symmetric-encryption-in-nodejs.

How can i pass the base64 string as a input certificate for Openssl?

I have a certificate which i am getting in string . I just want that string to be directly pass as -in in the openssl command.
But i don't see any method in openssl , can you help.
my code
const { exec } = require('child_process');
exec('openssl x509 -noout -issuer -in '+certificateString , (err, stdout, stderr) => {
if (err) {
console.log(err);
}else{
console.log(studout);
}
});
If i directly pass certificate file url it works, like this
openssl x509 -noout -issuer -in certificate.pem
this work
but how can i pass the certificate string directly in openssl ?
Any help?
First, do you have base64 or PEM? Those are not the same thing; although PEM includes base64, it is not only base64. If you have a file that works with openssl x509 -in file then it is PEM, NOT only base64. People seem to look at PEM, see more than 40 base64 characters, and their brains shut down and become unable to see the dashes-BEGIN line, the dashes-END line, and the line breaks, all of which are REQUIRED.
Second, certificate.pem is a filename or pathname, not a URL. A filename or pathname is not a URL, and a URL is not a filename or pathname, although some URL schemes (and particularly the only ones most people notice) include some elements in common with pathnames.
The certificate input to openssl x509 must be either a (named) file, or standard input (commonly abbreviated stdin). If you don't want to supply it as a named file, nodejs can provide data to a child's stdin, but not with the higher-level exec* methods, only the more basic spawn:
const{ spawn } = require('child_process');
const pem = `-----BEGIN CERTIFICATE-----
MIIDUzCCAjugAwIBAgIJANK2Ysp8bp+6MA0GCSqGSIb3DQEBCwUAMEAxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTETMBEGA1UEBwwKVGluc2VsdG93bjEPMA0GA1UE
CgwGRGlzbmV5MB4XDTE5MDUwOTA5MTQ0NVoXDTIwMDUwODA5MTQ0NVowQDELMAkG
A1UEBhMCVVMxCzAJBgNVBAgMAkNBMRMwEQYDVQQHDApUaW5zZWx0b3duMQ8wDQYD
VQQKDAZEaXNuZXkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCuKNJI
oBJ6acFSqMlG6e6WS44jC0RP+2y0q+02P4xc9BIuPjKV6/Lkg3bnYenAzktemvc7
EVFOUS/Ema4UIc+aDtSpjAegWnZNrzX+K76Xxzw+RnZalXB1Z++CpTdtsgSmkrmR
wJ7ZZpclAK+Yt6Ggx9ea3/d8WJ85V30ezcG7hPf5BrCSxzjSPsxG3heDPh1/X0zk
H7PD0JB+IW08yOikLmQNZeTZXaIAaSXoIPj5L9Ax7kyDEiDcSBIcQbPGMfIG6CPO
hKOM4yZKWni0mO9jwgfYNU6Bxei35/KTVwBWXHck9N7DdEtoST9THYO7ZFqqvTdk
mLfBpsPXorFT+vAVAgMBAAGjUDBOMB0GA1UdDgQWBBQyXFJDoapFe4JaZBD1xVYE
ImDj7DAfBgNVHSMEGDAWgBQyXFJDoapFe4JaZBD1xVYEImDj7DAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAwFXI13uxhGz+fem7N03r0+dnNaXZQQ9CR
owTHVVOjsfrsbFPhKdZIKMKeqpc1AksqynhbV2zY5VjINMab8ADw165gTCgy8/0S
X3QQsy2P5RNx/YuRMvs6hP7ZhZQlabLVbBnCWqAevT2qEZ7Gmi+m9A9sdK2Hsrkj
0lxGCozscme7E3ZfR/3GQVzyfZVppRLsgIth9F2y6SyLXwi+v39C+a9vdZjMS3Uy
HuRD9Sk8xydWywI8wKBlfnX4KGMBjKpSDpMeb6723eXuPC+soUBafuUoP+fWqjg4
LFgYg1TtyzfdrkkWZ9/KxS47OxkF6BAQtFGVF2nNgcpdXxToK7pP
-----END CERTIFICATE-----
`;
const p = spawn("/path/to/openssl", ["x509","-noout","-issuer"]);
// options.stdio defaults to ['pipe','pipe','pipe']
p.stdout.on('data',(data)=> {console.log(data.toString())} );
p.stderr.on('data',(data)=> {console.log("ERROR:"+data.toString())} );
p.on('close',()=> {} );
p.stdin.write(pem); p.stdin.end();
However, you don't need to run an external program to parse a certificate; there are lots of JS libraries to do it. For example, with the second one npmjs finds for me:
const { Certificate } = require('#fidm/x509');
const { ASN1 } = require('#fidm/asn1');
var iss = Certificate.fromPEM(pem).issuer.attributes;
var s = ""; for(var a of iss){ s += "/" + a.shortName + "=" + a.value; }
console.log(s);
If you actually did have base64 and not PEM, replace the third line with
const bin = Buffer.from(b64,'base64');
var iss = new Certificate(ASN1.fromDER(bin)).issuer.attributes;

Is there a way to create an AES cipher from a hashed key-secret?

Having lost the plaintext secret, but having the hashed key, it's posible to encript and decript in openssl like so (keys and iv are not the actual ones):
Input:
printf "ciphertext" | base64 -d | openssl enc -aes-256-cbc -d -nosalt -K "0000000000000000000000000000000000000000000000000000000000000000" -iv "00"
Output:
"plaintext"
Now, to be able to do this in a NodeJs application, openssl is called as child_process. As you can guess, spawning openssl calls is not very performant.
To be able to do it in node crypto, the plaintext "secret" is needed in creating the keys.
Is there a way yo generate the cipher from the hashed key?
I have tried doing it like so with no succes.
var crypto=require('crypto')
var iv = Buffer.alloc(16, 0);
var key = '0000000000000000000000000000000000000000000000000000000000000000'
var cipher=crypto.createDecipher('aes-256-cbc', newBuffer(key).toString('binary'), new Buffer('0000000000000000', 'hex').toString('binary'));
var enc = cipher.update("ciphertext", 'base64', 'utf8')
enc += cipher.final('utf8')
console.log(enc);
Output:
internal/crypto/cipher.js:164
const ret = this._handle.final();
^
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Try
new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex')
for the key and and
new Buffer('00000000000000000000000000000000', 'hex')
for the IV. Currently you are encoding to a binary string representation of the bytes, instead of (just) decoding the hexadecimal values to 32 bytes and 16 bytes respectively.
To use this you should use createDecipheriv as createDecipher will still generate the key using the password.

Node.js Crypto AES-128 encrypt stream with padding

I am successfully using NodeJS to encrypt a file in the following manner:
let cipher = crypto.createCipheriv('aes-128-cbc', key, iv),
crypted = cipher.update(file_data, 'binary');
return Buffer.concat([crypted, cipher.final()]);
This generates a file with a hash that exactly matches our target encryption, which is server-side OpenSSL command:
openssl aes-128-cbc -e -in file -out file.enc -nosalt -iv <iv_str> -K <k_str>
The goal is to upgrade our systems to use nodejs streams. Node documentation and other posts suggest the following way to encrypt streams:
cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
let file_stream = fs.createReadStream(local_file, 'binary'),
write_stream = fs.createWriteStream(`${local_file}.enc`, 'binary');
file_stream.pipe(cipher).pipe(write_stream);
however this ignores the cipher.final() appending. Without it the encryption always results in a file with the wrong hash. I've tried:
creating a Transform that would append the remaining padding. That doesn't work because you can't call final on a cipher that hasn't had update called on it
setting the autopadding with setAutoPadding. In all cases that results in the error Error: error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:data not multiple of block length
How can I cipher with streams in the same way I was originally encrypting?

What's wrong with nodejs crypto decipher?

I have the following encrypted data:
U2FsdGVkX1+21O5RB08bavFTq7Yq/gChmXrO3f00tvJaT55A5pPvqw0zFVnHSW1o
The pass to decrypt it is: password
(it's the example from gibberish-aes)
In the command line using openssl:
echo "U2FsdGVkX1+21O5RB08bavFTq7Yq/gChmXrO3f00tvJaT55A5pPvqw0zFVnHSW1o" | openssl enc -d -aes-256-cbc -a -k password
The output is:
Made with Gibberish\n
With my NodeJS application:
var decipher = crypto.createDecipher('aes-256-cbc', "password");
var dec = decipher.update("U2FsdGVkX1+21O5RB08bavFTq7Yq/gChmXrO3f00tvJaT55A5pPvqw0zFVnHSW1o",
'base64', 'utf8');
dec += decipher.final('utf8');
I have the following error TypeError: DecipherFinal fail at the decipher.final line.
Am I missing something ? Thanks.
The encrypted data starts with a 8 byte "magic" indicating that there is a salt (the ASCII encoding of "Salted__"). Then the next 8 bytes is the salt. Now the bad news: Node.js does not seem to use the salt for the EVP_BytesToKey method:
int key_len = EVP_BytesToKey(cipher, EVP_md5(), NULL,
(unsigned char*) key_buf, key_buf_len, 1, key, iv);
That NULL is the salt.
This has been verified using a Java test application (using the right salt) - the result string was returned.
Please leave out the salt using the OpenSSL -nosalt switch and try again.
[EXAMPLE]
OpenSSL CLI:
openssl enc -aes-256-cbc -nosalt -a -k password
owlstead
Mh5yxIyZH+fSMTkSgkLa5w==
NodeJS crypto:
var crypto=require('crypto')
var cipher=crypto.createDecipher('aes-256-cbc', "password")
var enc = cipher.update("Mh5yxIyZH+fSMTkSgkLa5w==", 'base64', 'utf8')
enc += cipher.final('utf8')
[LATE EDIT] Note that using secret key derivation with a salt and large work factor may be paramount to security. You'd better use a very unique, high entropy password otherwise your encrypted data may be at risk.
[REALLY LATE EDIT] OpenSSL 1.1.0c changed the digest algorithm used in some internal components. Formerly, MD5 was used, and 1.1.0 switched to SHA256. Be careful the change is not affecting you in both EVP_BytesToKey and commands like openssl enc.

Resources