why can't i decrypt a file with NodeJS that i encrypted with openssl? - node.js

i encrypted a file on the command line using
openssl aes-256-cbc -in /tmp/text.txt -out /tmp/text.crypt
i then tried to decrypt it using the following JavaScript code:
crypto = require( 'crypto' );
cipher_name = 'aes-256-cbc';
password = '*';
decoder = crypto.createDecipher( cipher_name, password );
text_crypt = njs_fs.readFileSync( '/tmp/text.crypt' );
chunks = [];
chunks.push decoder.update( text_crypt, 'binary' );
chunks.push decoder.final( 'binary' );
text = chunks.join( '' ).toString( 'utf-8' );
this fails with
TypeError: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
what am i doing wrong?

Cryptography is fun. Here is the code that decrypts file encrypted with openssl with salt.
var crypto = require('crypto');
function md5(data) {
var hash = crypto.createHash('md5');
hash.update(data);
return new Buffer(hash.digest('hex'), 'hex');
}
var text = require('fs').readFileSync('text.crypt');
var salt = text.slice(8, 16);
var cryptotext = text.slice(16);
var password = new Buffer('*');
var hash0 = new Buffer('');
var hash1 = md5(Buffer.concat([ hash0, password, salt ]));
var hash2 = md5(Buffer.concat([ hash1, password, salt ]));
var hash3 = md5(Buffer.concat([ hash2, password, salt ]));
var key = Buffer.concat([ hash1, hash2 ]);
var iv = hash3;
var decoder = crypto.createDecipheriv('aes-256-cbc', key, iv);
var chunks = [];
chunks.push(decoder.update(cryptotext, "binary", "utf8"));
chunks.push(decoder.final("utf8"));
console.log(chunks.join(''));
Update: more details on what is cbc mode and how openssl works
If you look on how the stream ciphers in cipher-block chaining mode work you will notice that two initial values are required for the cipher to start encrypting the data: initialization vector (iv) and the key. It is important that size of the initialization vector should be equal to the block size and the key size depends on the algorithm, for AES-256 it is 256-bit long.
But users do not want to set up 256-bit random passwords to access their data. This opens a question on how to construct the key and iv from the user's input and openssl solves it by applying EVP_BytesToKey function to the user input, which is practically MD5 applied to the password and salt multiple times.
You can see the derived values by executing
C:\Tools\wget>openssl enc -aes-256-cbc -P
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:
salt=A94B7976B2534923
key=C8B806C86E60ED664B9C369628D1A78260753580D78D09EAEC04EAC1535077C3
iv =7B6FB26EB62C34F04F254A0C4F4F502A
The parameters "key and "iv" here are the input parameters to the cipher and the salt is required to randomize the cipher-text so it will not be the same for the same data.
The openssl saves the data in the file as follows:
Saltet__;[salt][cipher-text]
So to decrypt it the following steps should be made:
the "Salted" prefix should be skipped
8 bytes of the input should be read and saved as salt
from the password and salt the key and iv should be constructed
the rest of the file should be decrypted by applying AES-256-CBC decryptor with calculated key and iv
The code above performs these steps and decrypts the file.

Related

nodejs recover createCipher data with createCipheriv

I have some encrypted data in my database
I did it few years ago using crypto.createCipher
const cipher = crypto.createCipher('aes192', password);
As createCipher and createDecipher is deprecated, I would like to change to createCipheriv and createDecipheriv. The problem is that the data I have in my database are encoded without iv.
Is it possible to decode with createDecipheriv data encoded with createDecipher and to generate the same secret with createCipher and createCipheriv.
I tried setting the iv to null but not working
Thanks, because the database migration is an heavy work !
I tried setting the iv to null but not working
This is because this method didn’t allow for passing an initialization vector (IV), and instead derived the IV from the key using the OpenSSL EVP_BytesToKey derivation function, using a null salt meaning that the IV would be deterministic for a given key which is an issue for ciphers with counter mode like CTR, GCM and CCM.
Looking at your code:
const cipher = crypto.createCipher('aes192', password);
If you want to make this code backwards compatible, you need to call OpenSSL’s EVP_BytesToKey function yourself, typically through evp_bytestokey module which makes it available in JS userland.
Is it possible to decode with createDecipheriv data encoded with createDecipher and to generate the same secret with createCipher and createCipheriv.
Yes, you can. check out my example code here:
const crypto = require('crypto');
const EVP_BytesToKey = require('evp_bytestokey')
const ALGO = 'aes192';
const password = 'Your_Password_Here';
const KEY_SIZE = 24;
function decrypt_legacy_using_IV(text) {
const result = EVP_BytesToKey(
password,
null,
KEY_SIZE * 8, // byte to bit size
16
)
let decipher = crypto.createDecipheriv(ALGO, result.key, result.iv);
let decrypted = decipher.update(text, 'hex','utf8') + decipher.final('utf8');
return decrypted.toString();
}
function encrypt_legacy_using_IV(text) {
const result = EVP_BytesToKey(
password,
null,
KEY_SIZE * 8, // byte to bit size
16
)
var cipher = crypto.createCipheriv(ALGO, result.key, result.iv);
var encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
return encrypted.toString();
}
For complete running example, clone node-snippets and run node apogee-legacy-crypto-cipheriv.js.
However the reason this function is deprecated in the first place is because you shouldn’t use it, and instead use a random unpredictable IV, which requires you to change your code to something like this:
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv('aes192', password, iv)
Here, for AES-192 in CBC mode (aes192 being aliased to AES-192-CBC by OpenSSL), the IV size is expected to be the same as the block size, which is always 16 bytes.
In order to decrypt the message, you will need the IV as well. Typically you’d store the IV together with the message, as the important part is for the IV to not be predictable ahead of time.

Is there a way to get same encrypted hash value with some secret key?

I want to encrypt sensitive data in an encrypted format and save it to db. But later I have to be able to decrypt with a secret key used to decrypt. Importantly, encryption must give always the same hash.
const algorithm = 'aes256';
const iv = crypto.randomBytes(16).toString('hex').slice(0, 16);
const key = crypto
.createHash('sha256')
.digest('base64')
.substr(0, 32);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encrypted =
cipher.update(String('tobeEncrypted'), 'utf8', 'hex') + cipher.final('hex');
console.log(encrypted);
console.log(encrypted);
//e08f733a4dace8b22db763cbd2d0029e
//90086251f083c33dd6aa017a2c6f35f4
// How can I always get the same hash value?
First, your key will be the same key value. Because the value to be hashed will be empty.
const key = crypto
.createHash("sha256") // Hash algorithm
.update(process.env.SECRET_KEY) // Data to hash
.digest('base64')
.substr(0, 32);
Your result will be always different because the IV is random in each execution. So, you could store the IV in the database, in the final message, or use a unique depending on other values like the key or the data.
There is no security risk if you save the IV in your database or if you expose it.
Refs:
Is it safe to store AES IV prepended to CipherText in a DB?
When using AES and CBC, is it necessary to keep the IV secret?

Trying to decrypt an encrypted key generated using AES 256(AES/ECB/PKCS7Padding) algorithm in nodejs using crypto

I tried using aes256,aes-cross, and crypto.
But I was not able to decrypt the key which is encrypted using AES 256 (aes-256-ecb) with PKCS7 padding.
I ended up with the following mentioned errors.
Error: Invalid key length
at Decipheriv.createCipherBase
(or)
Error: Invalid IV length
at Decipheriv.createCipherBase
I was not able to find an npm package that helps me.
Here is the sample code:
const crypto = require("crypto");
//Length of my key is 32
const key = Buffer.from("aaaaaabbbbbbccccccddddddssssssaa", "base64");
//_sek is the encrypted key
const _sek = "NEPkEuWaXZUawBHJZIcMjHJeKuPkaQezuRc3bjWEezlbHzmqCSyh2hazB+WeAJaU"
const cipher = crypto.createDecipheriv(
"aes-256-ecb",
Buffer.from(key, "base64"),
Buffer.from([])
);
return cipher.update(_sek, "base64", "utf8") + cipher.final("utf8");
If anyone could help me with a code-based example in nodejs. It will help me to understand clearly.
Update:
function decrypt(encryted_key, access_key) {
var key = Buffer.from(access_key, "base64");
const decipher = crypto.createDecipheriv("aes-256-ecb", key, "");
decipher.setAutoPadding(false);
var decryptedSecret = decipher.update(encryted_key, "utf8", "base64");
decryptedSecret += decipher.final("base64");
return decryptedSecret;
}
decrypt(
"w2lI56OJ+RqQ04PZb5Ii6cLTxW2bemiMBTXpIlkau5xbmhwP4Qk3oyIydKV1ttWa",
"DvpMLxqKlsdhKe9Pce+dqTdNUnrofuOQPsgmSHhpxF8="
)
Required output: "cdgLxoHvpeMoMd3eXISoMcgQFRxZeMSez5x3F2YVGT4="
But got this : "G7z/eXQefnaeB7mYBq7KDrH+R4LtauNi6AU1v0/yObqoOidSOkIeW085DiMxdCDDjaI+hJiS2JRHDL1fdLrveg="
Thanks in advance.
The Invalid key length error is caused when the length of the key doesn't match the specified algorithm. E.g. in the posted code aes-256-ecb is specified, which defines AES-256, i.e. AES with a key length of 32 bytes. However, the key used has a length of only 24 bytes, since it's Base64 decoded when it's read into the buffer. This means that either a 32 bytes key must be used (e.g. if UTF-8 is used as encoding instead of Base64 when reading into the buffer), or AES-192 (specified as aes-192-ecb).
The Invalid IV length error is caused when an IV is specified in ECB mode (which doesn't use an IV at all), or when in a mode which uses an IV, its length doesn't match the blocksize of the algorithm (e.g. 16 bytes for AES). Since the ECB mode is used here, simply pass null for the IV (Buffer.from([]) works too).
Example using AES-192 and a 24 bytes key:
const crypto = require("crypto");
const key = "aaaaaabbbbbbccccccddddddssssssaa";
const secret = "01234567890123456789012345678901";
// Encryption
const cipher = crypto.createCipheriv("aes-192-ecb", Buffer.from(key, "base64"), null);
const encryptedSecret = cipher.update(secret, "utf8", "base64") + cipher.final("base64");
console.log(encryptedSecret);
// Decryption
const decipher = crypto.createDecipheriv("aes-192-ecb", Buffer.from(key, "base64"), null);
const decryptedSecret = decipher.update(encryptedSecret, "base64", "utf8") + decipher.final("utf8");
console.log(decryptedSecret);
During decryption, UTF-8 is used as output encoding which is of course only possible if the plaintext is compatible with this, otherwise a suitable encoding such as Base64 has to be applied.

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.

Nodejs, Crypto: Encrypting multiple strings using same cipher

I am trying to encrypt multiple strings using same cipher with the code
var iv = _crypto.randomBytes(16).slice(0, 12);
var salt = _crypto.randomBytes(64);
var key = _crypto.pbkdf2Sync(symmetricKey, salt, 2145, 32, 'sha512');
var cipher = _crypto.createCipheriv('aes-256-gcm', key,iv);
var a = Buffer.concat([cipher.update("data1", 'utf8'), cipher.final()]);
var b = Buffer.concat([salt, iv, a]).toString('base64');
var c = Buffer.concat([cipher.update("data2", 'utf8'), cipher.final()]);
The execution failed in the last line without showing any error.
"TypeError: error:00000000:lib(0):func(0):reason(0)"
On investigating further, I came to know that we can't use the cipher once we have done cipher.final(). But if I won't do it earlier(during encryption of data1), during decryption of "encrypted format of data1", it would fail since cipher.final returns any remaining enciphered contents which gets appended with the original encrypted string.
What's the best way to encrypt multiple strings then, or should I create separate ciphers for all the strings?

Resources