5

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?

Karel Bílek
  • 36,467
  • 31
  • 94
  • 149
  • [OpenSSL 1.1.0c changed the digest algorithm](http://stackoverflow.com/q/39637388/608639) 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`. – jww Jan 26 '17 at 16:21

1 Answers1

3

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.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • In the case of GCM mode (first time I am hearing about it!), you don't need to do MAC explicitly and can just assume the authentication will work, right? – Karel Bílek Nov 27 '15 at 20:16
  • 1
    That's right and you can use any approach above. Don't forget to get the authentication tag with `cipher.getAuthTag()` and send it along with the ciphertext. The receiver should call `decipher.setAuthTag(buffer)` before finishing the decryption and check for exceptions on calling `decipher.final` – Artjom B. Nov 27 '15 at 20:57
  • Another question. Is it OK to use the hashed value as pseudo-random keys/IVs, as in your first code example? Even when it's not directly the data from pbkdf? Don't I lose some entropy by doing the HMAC? – Karel Bílek Nov 27 '15 at 21:37
  • 1
    I don't think this is an issue at all. If you really want to know, then I would suggest that you ask this on [crypto.se], but check for duplicates first. I haven't found a question that goes into that direction, but I think it must be there. Maybe it's on [security.se]. – Artjom B. Nov 27 '15 at 22:07
  • I made a follow up question here - https://stackoverflow.com/questions/33976117/does-node-js-crypto-use-fixed-tag-size-with-gcm-mode . (I feel it's kind of lazy to ask everything on SO, but then, I will not be the only one asking this, at least the information will be saved somewhere.) – Karel Bílek Nov 28 '15 at 20:28