Is there a way to get same encrypted hash value with some secret key? - node.js

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?

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.

Node.js crypto: too short encryption key

I want to encrypt a user's data with AES-256 to store it securely in my database. However, I have the problem that the key must be 32 characters long. But the passwords of my users are usually much shorter. Is there a way how I can "extend" the length of the passwords?
I also thought about the fact that human-made passwords are usually weak. So I would need some kind of function that "links" the password to the encryption key?
Here is my code, which I use to encrypt and decrypt:
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const key; //Here I would get the password of the user
function encrypt(text) {
const iv = crypto.randomBytes(16);
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return { iv: iv.toString('hex'), encryptedData: encrypted.toString('hex') };
}
function decrypt(text) {
let iv = Buffer.from(text.iv, 'hex');
let encryptedText = Buffer.from(text.encryptedData, 'hex');
let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
Many thanks for answers in advance.
Update 1.0:
After some research I have found the following code: (Source)
const crypto = require('crypto');
// Uses the PBKDF2 algorithm to stretch the string 's' to an arbitrary size,
// in a way that is completely deterministic yet impossible to guess without
// knowing the original string
function stretchString(s, outputLength) {
var salt = crypto.randomBytes(16);
return crypto.pbkdf2Sync(s, salt, 100000, outputLength, 'sha512');
}
// Stretches the password in order to generate a key (for encrypting)
// and a large salt (for hashing)
function keyFromPassword(password) {
// We need 32 bytes for the key
const keyPlusHashingSalt = stretchString(password, 32 + 16);
return {
cipherKey: keyPlusHashingSalt.slice(0, 32),
hashingSalt: keyPlusHashingSalt.slice(16)
};
}
If I got everything right, this should solve my problem: From any password I can generate a secure encryption key with a given length using the above function. The same password always generates the same encryption key with the function keyFromPassword(password), right?
Update 2.0:
Thanks to #President James K. Polk, who gave me some important tips, I have now updated my code. I hope that everything is fine now.
You should not be using your user's passwords directly as keys. Instead, you can use them as input to a key-derivation algorithm like pkbdf2.
As this paper on PKBDF2 explains:
Unfortunately, user-chosen passwords are generally short and
lack enough entropy [11], [21], [18]. For these reasons, they cannot
be directly used as a key to implement secure cryptographic systems. A possible solution to this issue is to adopt a key derivation
function (KDF), that is a function which takes a source of initial
keying material and derives from it one or more pseudorandom keys.
A library for computing pkbdf2 for Node.js, for example is found here.
Always add a salt to the text, and append it to the end of the text. The salt can be a randomly generated string of arbitrary length and can easily satisfy the 32-char length limit. In addition, adding salts before encryption strengthens the encryption.

Do string decryption require guid and iv (initialization vector)

I am doing string encryption like below using the guid and storing the cipherout.ciphertext value but not storing the cipherout.iv value.
But when decrypting , why cant i just pass the key , instead of passing both key and cipherout.iv values. Because it is asking for both values while decrypting it. Do i need to store both key and cipherout.iv values for decrypting?
Please advice
FIRSTFILE.js :
var guid = "4ab23a136dc347d";
var inputString = "sometext";
// Create the key
var key = crypto.createSecretKey({guid:guid, encoding:encode.Encoding.UTF_8});
// Encrypt
var cipher = crypto.createCipher({algorithm: crypto.EncryptionAlg.AES, key: key});
cipher.update({input: inputString, inputEncoding: encode.Encoding.UTF_8});
var cipherout = cipher.final({outputEncoding: encode.Encoding.HEX});```
SECONDFILE.JS
// Decrypt
var decipher = crypto.createDecipher({algorithm: crypto.EncryptionAlg.AES, key: key, iv:cipherout.iv}); //HERE
decipher.update({input: cipherout.ciphertext, inputEncoding: encode.Encoding.HEX});
var decipherout = decipher.final({outputEncoding: encode.Encoding.UTF_8});```
Yes, You need to store both. Without initial vector it's not possible to decrypt message even with password. The idea is: password can be same for all messages, but IV is created when it was encrypted and it's different always.
IV can be encrypted with 4096 RSA, but all message cab be too long for RSA and you encryp it with AES.
AES is not good from security perspective, so we need use password AND one-time-key - initial vector.

How to derive IV and key to crypto.createCipheriv for decryption?

I have seen other questions which ask about creating the initialization vector (IV) for encryption and it seems using a random value is one option. However, I need to generate the IV for decryption, so I have to use the same one that the data was encrypted with based on some salt.
The node.js crypto function createDecipher says:
The implementation of crypto.createDecipher() derives keys using the
OpenSSL function EVP_BytesToKey with the digest algorithm set to MD5,
one iteration, and no salt.
For backwards compatibility with assets encrypted by other software, I need a different number of iterations, and a salt that I specify.
Continuing to read the documentation, it further says:
In line with OpenSSL's recommendation to use PBKDF2 instead of
EVP_BytesToKey it is recommended that developers derive a key and IV
on their own using crypto.pbkdf2() and to use crypto.createDecipheriv()
to create the Decipher object.
Ok, that sounds good. The data I need to decrypt was encrypted using EVP_BytesToKey to get the key and IV, so I need to be compatible with that, though.
Anyway, the crypto.pbkdf2 function appears to take all the parameters I need it to, but the problem is, it does not appear to create an initialization vector.
The corresponding C code which did the decryption which this needs to be compatible with looks like this:
// parameters to function:
// unsigned char *decrypt_salt
// int nrounds
// unsigned char *decrypt_key_data <- the password
// int decrypt_key_data_len <- password length
// the following is not initialized before the call to EVP_BytesToKey
unsigned char decrypt_key[32], decrypt_iv[32];
EVP_BytesToKey(EVP_aes_256_cbc(), EVP_md5(), decrypt_salt, decrypt_key_data,
decrypt_key_data_len, nrounds, decrypt_key, decrypt_iv);
My attempt to use crypto.pbkdf2 to replicate this behavior:
crypto.pbkdf2(password, salt, nrounds, 32, "md5", (err, derivedKey) => {
if (err) throw err
console.log(derivedKey.toString("hex"))
})
The derivedKey also does not match the key produced by the C code above. I'm not sure if that's even expected! I also tried key lengths of 48 and 64 but those didn't generate anything similar to the expected key and IV either.
Given the correct password, salt, and hashing rounds, how do I generate the same key and IV to decrypt with?
To start, the reason you are not getting your desired result is because the C code you have does use EVP_BytesToKey, whereas your NodeJS code uses PBKDF2. I think you may have misunderstood the recommendation of OpenSSL. They recommend PBKDF2, not as a better way to produce the same result, but as a better way to solve the problem. PBKDF2 is simply a better key derivation function, but it will not produce the same result as EVP_BytesToKey.
Further, the way you are handling your IV generation in the first place is quite poor. Using a KDF to generate your key is excellent, well done. Using a KDF to generate an IV is, frankly, quite a poor idea. Your initial readings, where you found that generating an IV randomly is a good idea, are correct. All IVs/nonces should be generated randomly. Always. The important thing to keep in mind here is that an IV is not a secret. You can pass it publicly.
Most implementations will randomly generate an IV and then prefix it to the ciphertext. Then, when it comes to decrypting, you can simply remove the first 128-bits (AES) worth of bytes and use that as the IV. This covers all your bases and means you don't have to derive your IV from the same place as the key material (which is yucky).
For further information, see the examples in this GitHub repository. I have included the NodeJS one below, which is an example of best-practice modern encryption in NodeJS:
const crypto = require("crypto");
const ALGORITHM_NAME = "aes-128-gcm";
const ALGORITHM_NONCE_SIZE = 12;
const ALGORITHM_TAG_SIZE = 16;
const ALGORITHM_KEY_SIZE = 16;
const PBKDF2_NAME = "sha256";
const PBKDF2_SALT_SIZE = 16;
const PBKDF2_ITERATIONS = 32767;
function encryptString(plaintext, password) {
// Generate a 128-bit salt using a CSPRNG.
let salt = crypto.randomBytes(PBKDF2_SALT_SIZE);
// Derive a key using PBKDF2.
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
// Encrypt and prepend salt.
let ciphertextAndNonceAndSalt = Buffer.concat([ salt, encrypt(new Buffer(plaintext, "utf8"), key) ]);
// Return as base64 string.
return ciphertextAndNonceAndSalt.toString("base64");
}
function decryptString(base64CiphertextAndNonceAndSalt, password) {
// Decode the base64.
let ciphertextAndNonceAndSalt = new Buffer(base64CiphertextAndNonceAndSalt, "base64");
// Create buffers of salt and ciphertextAndNonce.
let salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE);
let ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE);
// Derive the key using PBKDF2.
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
// Decrypt and return result.
return decrypt(ciphertextAndNonce, key).toString("utf8");
}
function encrypt(plaintext, key) {
// Generate a 96-bit nonce using a CSPRNG.
let nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE);
// Create the cipher instance.
let cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce);
// Encrypt and prepend nonce.
let ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ]);
return Buffer.concat([ nonce, ciphertext, cipher.getAuthTag() ]);
}
function decrypt(ciphertextAndNonce, key) {
// Create buffers of nonce, ciphertext and tag.
let nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE);
let ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE);
let tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE);
// Create the cipher instance.
let cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce);
// Decrypt and return result.
cipher.setAuthTag(tag);
return Buffer.concat([ cipher.update(ciphertext), cipher.final() ]);
}

why can't i decrypt a file with NodeJS that i encrypted with openssl?

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.

Resources