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

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() ]);
}

Related

PBEWithHmacSHA256AndAES_128 encryption in nodejs

I'm trying to encrypt a string using PBEWithHmacSHA256AndAES_128 in nodejs, however I'm having a bit of trouble determining the correct way to do it.
Lots of documentation state I can use the crypto library, which when I try crypto.getCiphers() I see 'aes-128-cbc-hmac-sha256' is supported.
I've tried various tutorials, https://www.geeksforgeeks.org/node-js-crypto-createcipheriv-method/ and such but I'm mainly hitting "Invalid key length" or "Invalid initialization vector" when I try to change the cipher type.
Could anyone point me to some documentation or code samples that may assist in achieving this?
PBEWithHmacSHA256AndAES_128 and aes-128-cbc-hmac-sha256 refer to different things.
Both encrypt with AES-128 in CBC mode, but the former uses a key derivation, the latter a MAC (more precisely an HMAC) for authentication.
Regarding NodeJS, the latter has apparently never worked reliably. In some versions exceptions are generated, in others no authentication is performed (i.e. the processing is functionally identical to AES-128-CBC), see here. This is not surprising since OpenSSL only intends this to be used in the context of TLS, see here, which of course also applies to NodeJS as this is just an OpenSSL wrapper.
But since you are concerned with PBEWithHmacSHA256AndAES_128, the aes-128-cbc-hmac-sha256 issues are in the end not relevant. PBEWithHmacSHA256AndAES_128 uses PBKDF2 (HMAC/SHA256) as key derivation, which is supported by NodeJS. A possible implementation that is functionally identical to PBEWithHmacSHA256AndAES_128 is:
var crypto = require("crypto");
// Key derivation
var password = 'my passphrase';
var salt = crypto.randomBytes(16); // some random salt
var digest = 'sha256';
var length = 16;
var iterations = 10000;
var key = crypto.pbkdf2Sync(password, salt, iterations, length, digest);
// Encryption
var iv = crypto.randomBytes(16); // some random iv
var cipher = crypto.createCipheriv('AES-128-CBC', key, iv);
var encrypted = Buffer.concat([cipher.update('The quick brown fox jumps over the lazy dog', 'utf8'), cipher.final()]);
// Output
console.log(salt.toString('base64')); // d/Gg4rn0Gp3vG6kOhzbAgw==
console.log(iv.toString('base64')); // x7wfJAveb6hLdO4xqgWGKw==
console.log(encrypted.toString('base64')); // RbN0MsUxCOWgBYatSbh+OIWJi8Q4BuvaYi6zMxqERvTzGtkmD2O4cmc0uMsuq9Tf
The encryption with PBEWithHmacSHA256AndAES_128 gives the same ciphertext when applying the same parameters. This can be checked e.g. with Java and the SunJCE provider which supports PBEWithHmacSHA256AndAES_128 (here).
Edit:
From the linked Java code for decryption all important parameters can be extracted directly:
var crypto = require("crypto");
// Input parameter (from the Java code for decryption)
var password = 'azerty34';
var salt = '12345678';
var digest = 'sha256';
var length = 16;
var iterations = 20;
var iv = password.padEnd(16, '\0').substr(0, 16);
var plaintext = '"My53cr3t"';
// Key derivation
var key = crypto.pbkdf2Sync(password, salt, iterations, length, digest);
// Encryption
var cipher = crypto.createCipheriv('AES-128-CBC', key, iv);
var encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
// Output
console.log(encrypted.toString('base64')); // bEimOZ7qSoAd1NvoTNypIA==
Note that the IV is equal to the password, but truncated if it is larger than 16 bytes or padded with 0x00 values at the end if it is shorter than 16 bytes (as is the case here).
The NodeJS code now returns the required ciphertext for the given input parameters.
Keep in mind that the static salt is a serious security risk.

Why createCipheriv and createDecipheriv are not working in separate functions - crypto

I am starting to use crypto module for my NodeJS project.
My code is simple as below:
const { createCipheriv, randomBytes, createDecipheriv } = require('crypto');
const key = randomBytes(32);
const iv = randomBytes(16);
const cipher = createCipheriv('aes256', key, iv);
const decipher = createDecipheriv('aes256', key, iv);
const cipherTest = (message) => {
const encryptedMessage = cipher.update(message, 'utf8', 'hex') + cipher.final('hex');
const decryptedMessage = decipher.update(encryptedMessage, 'hex', 'utf-8') + decipher.final('utf8');
return decryptedMessage.toString('utf-8')
}
The cipherTest function returns the same result as input. Correct!
However, If I create the cipher and decipher functions separately as below , It can not decipher the encryptedMessage.
const encryptMessage = (message) => {
const encryptedMessage = cipher.update(message, 'utf8', 'hex') + cipher.final('hex');
return encryptedMessage;
}
const decryptMessage = (encryptedMessage) => {
const decryptedMessage = decipher.update(encryptedMessage, 'hex', 'utf-8') + decipher.final('utf8');
return decryptedMessage;
}
Could someone have a look? I am so thankful for that.
Ciphers are algorithms, but they contain state. Furthermore, they usually require an IV as input, which needs to be randomized for CBC which is used in this case. That IV needs to change for each ciphertext, assuming that the key remains the same.
As such, you should generally keep to the following structure in pseudo-code:
MessageEncryptor {
Key key
constructor(Key key) {
if (key = bad format) {
throw error // fail fast
this.key = key
}
String encryptMessage(String message) {
Cipher cipher = Cipher.Create(key)
Bytes iv = Rng.createBytes(cipher.blockSize)
cipher.setIV(iv)
Bytes encodedMessage = UTF8.encode(message)
// lower level runtimes may require padding for CBC mode
Bytes ciphertext = cipher.encrypt(encodedMessage)
ciphertext = Bytes.concat(ciphertext, cipher.final())
Bytes ciphertextMessage = Bytes.concat(iv, ciphertext)
// use HMAC here to append an authentication tag
String encodedCiphertextMessage = Base64.encode(ciphertextMessage)
return encodedCiphertextMessage
}
String decryptMessage(String encodedCiphertextMessage) {
Cipher cipher = Cipher.Create(key)
Bytes ciphertextMessage = Base64.decode(encodedCiphertextMessage)
// use HMAC here to verify an authentication tag
Bytes iv = Bytes.sub(0, cipher.blockSizeBytes)
cipher.setIV(iv)
Bytes encodedMessage = cipher.decrypt(ciphertextMessage, start = iv.Size)
encodedMessage = Bytes.concat(encodedMessage, cipher.final())
// lower level runtimes may require unpadding here
String message = UTF8.decode(encodedMessage)
return message
}
}
As you can see it is imperative that you:
only use the Cipher construct within the method bodies; these object carry state and are generally not thread-safe as they are mutable;
rely on the key as a field so that the message encryptor always uses the same key;
create a new MessageEncryptor instance in case you want to use another key;
create a new IV per message during encryption and extract it from the ciphertext message during decryption.
Notes:
if your scheme can handle bytes then there is no need to encode the ciphertextMessage;
the IV may have a different size or may not require random values for other modes such as GCM;
using an authenticated mode such as GCM is much safer, but hashing the IV + ciphertext using HMAC is also an option (use a separate key if you are unsure about possible issues for reusing the encryption key);
UTF8.encode and UTF8.decode can simply be replaced by any other codec in case your message is not a simple string (UTF8 is highly recommended for strings though);
a lot of libraries will have shortcuts for encoding and such, so your code may be shorter;
there are also pre-made container formats such as CMS or NaCL which are probably more secure and more flexible than any scheme you can come up with;
CBC, as used in this example, is vulnerable to plaintext oracle attacks including padding oracle attacks
for larger messages it makes more sense to use streaming for all encoding, decoding, encryption and decryption operations (the methods shown here have a high and unnecessary memory requirement);
create these kind of classes only for specific types of messages, and create a protocol + protocol description if you do so; most crypto API's don't need wrapper classes, it was created with this kind of flexibility on purpose;
The MessageEncryptor still is very lightweight - actually it has only one key as state (sometimes having a an additional key and an algorithm or two as well). If you are going to expand with fields that can change value or state such as a buffer then you want to create a new instance of the class each time you use it from a different thread. Otherwise you want to clear the state after each successful message encryption / decryption.
You may want to destroy the key securely once you've finished using it.

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.

hash password using node crypto scryptSync

I need to secure the user input password of an electron app to create a key for crypto AES ancryption. As suggested here on SO I've replaced the createHash function with scryptSync function. I'm not sure about the salt.
// this is the old method I've used to hash the password to obtain the correct lenght for aes-256-gcm
// let key = crypto.createHash('sha256')
// .update(data.password)
// .digest();
// salt creation
let salt = crypto.randomBytes(64);
let key = crypto.scryptSync(data.password, salt, 32);
Is there a predefined size that I need to pass to randomBytes function for aes-256-gcm encryption algo?
Also when I attach the salt to the encrypted output I'm using this code
let encryptedData = Buffer.concat([cipher.update(base64, 'utf8'), cipher.final()]);
let payload = `${salt.toString('base64')}:${iv.toString('base64')}:${encryptedData.toString('base64')}`;
Since I'm using the join() function to merge multiple encoded strings, I need a way to split them when the encrypted output file needs to be decoded. At the moment I'm using this way, but I've figured out that I need to extract the salt to check the password. Is there a way to do it better?
const decryptData = (data) => {
let output = [];
// I need the salt here but the file needs to be splitted before
let key = crypto.scryptSync(data.password, 'salt', 32);
return new Promise( (resolve, reject) => {
fs.readFile(data.path, 'utf8', (err, content) => {
if(err) return reject(err);
let fileContents = content.split(' ');
output = fileContents.map( (file) => {
let [salt, iv, content] = file.split(':');
let cipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(iv, 'base64'));
let decrypted = cipher.update(content, 'base64', 'utf8');
return decryptedData;
});
resolve(output);
});
});
}
As the Node.js indicates, the hash needs to be minimally 128 bits or 16 bytes. 64 bytes is really too much, I'd max out on 256 bits but 128 is really enough when it comes to a password. As you seem to be aiming at max security, try 32 bytes. Don't forget that requiring a strong password is at least as important, if not more important.
Currently you are using the default cost parameter for scryptSync. That's probably not a good idea as it is set to 16384 by default. The higher the better, so try and see what's best for your particular target platforms.
Note that you both know the salt and IV size in bytes. This means that it would be just as easy to get them from a binary string before it is encoded to base 64. That also has some advantages: if you load the binary data into a Buffer then you can simply use methods like from to get a different view, which can then represent an IV or salt without copying the data.
That's most important for the ciphertext of course. You want to copy that as few times as possible.

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.

Resources