Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the correct settings for crypto.pbkdf2 to derive IV and key to crypto.createCipheriv?

In an application in node.js, I am using crypto module for symmetric encryption/decryption.

I am using AES-256-CTR. I originally assumed the crypto.createCipher will be "just working" and "handwaved" the details. Now I am reading in the documentation:

Note: createCipher derives keys with the OpenSSL function EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt. The lack of salt allows dictionary attacks as the same password always creates the same key. The low iteration count and non-cryptographically secure hash algorithm allow passwords to be tested very rapidly.

In line with OpenSSL's recommendation to use pbkdf2 instead of EVP_BytesToKey it is recommended you derive a key and iv yourself with crypto.pbkdf2 and to then use createCipheriv() to create the cipher stream.

All right, I can derive the IV and key myself.

But, I am not sure, what is the correct and recommended way of doing that - should I do key derivation separately for both, with different salts? Should I do one key derivation and then cut it in half? Should I use salt at all for this specific use-case? Should I randomly generate the salt and save it with the data?

like image 838
Karel Bílek Avatar asked Oct 18 '22 21:10

Karel Bílek


1 Answers

should I do key derivation separately for both, with different salts?

You can certainly do that, but a faster alternative with roughly the same security would be to use something like this:

var master = crypto.pbkdf2Sync(password, randomSalt, 60000, 256, 'sha256');
var hmac = crypto.createHmac('sha256', master);
hmac.update("key");
var key = hmac.digest();

hmac = crypto.createHmac('sha256', master);
hmac.update("nonce");
var nonce = hmac.digest().slice(0,12); // 96 bit for CTR nonce

Should I do one key derivation and then cut it in half?

Requesting more output bytes than the underlying hash function provides is problematic. If you want an AES-256 key (256 bits) and a nonce (IV) of 64 to 128 bit then you would need to use either SHA-384 (sha384) or SHA-512 (sha512) as the underlying digest which are both provided by node.js.

Should I randomly generate the salt and save it with the data?

Yes, you need to send the salt along with the ciphertext so that the receiver can use the password they have and the salt in order to generate the same key+nonce.

Perhaps you meant the nonce itself. That would be a third option that you have to generate the nonce randomly and store it alongside of the random (encryption) salt and the ciphertext.

Conclusion

All of the above ways provide roughly the same security, but they differ in what is included in the ciphertext and the additional computation time. I would suggest to use the way that is the easiest, because ...

You also should implement ciphertext authentication. If you don't then your system might be vulnerable to a padding oracle attack.

You could either use the first suggestion with an additional key for an encrypt-then-MAC solution with as:

hmac = crypto.createHmac('sha256', master);
hmac.update("hmac");
var hmacKey = hmac.digest();

// TODO encrypt

hmac = crypto.createHmac('sha256', hmacKey);
hmac.update(ciphertext);
var authenticationTag = hmac.digest();

then you also need to include the authentication tag with the ciphertext and check that it matches on the receiver side before decryption.

You can also use an authenticated mode like GCM which node.js supports.

like image 148
Artjom B. Avatar answered Nov 15 '22 05:11

Artjom B.