crypto.pbkdf2 派生 IV 和密钥 crypto.createCipheriv 的正确设置是什么?

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

在 node.js 中的应用程序中,我使用 crypto module 进行对称 encryption/decryption。

我正在使用 AES-256-CTR。我最初假设 crypto.createCipher 将是 "just working" 和 "handwaved" 的详细信息。现在我正在阅读文档:

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.

好的,我可以自己导出IV和密钥。

但是,我不确定这样做的正确和推荐方法是什么 - 我是否应该使用不同的盐分别对两者进行密钥派生?我应该做一个密钥推导然后减半吗?对于这个特定的用例,我应该完全使用盐吗?我应该随机生成盐并将其与数据一起保存吗?

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

您当然可以这样做,但是具有大致相同安全性的更快的替代方法是使用这样的方法:

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?

请求比底层哈希函数提供的更多的输出字节是有问题的。如果您想要一个 AES-256 密钥(256 位)和一个 64 到 128 位的随机数 (IV),那么您需要使用 SHA-384 (sha384) 或 SHA-512 (sha512) 作为基础 digest 均由 node.js.

提供

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

是的,您需要将盐与密文一起发送,以便接收方可以使用他们拥有的密码和盐来生成相同的密钥+随机数。

也许你指的是 nonce 本身。这将是第三种选择,您必须随机生成随机数并将其与随机(加密)盐和密文一起存储。

结论

以上所有方法提供的安全性大致相同,但它们在密文中包含的内容和额外的计算时间上有所不同。我建议使用最简单的方法,因为 ...

您还应该实施密文身份验证。如果您不这样做,那么您的系统可能容易受到填充 oracle 攻击。

您可以将第一个建议与附加密钥一起用于加密然后-MAC 解决方案,如:

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

那么你还需要在密文中包含身份验证标签,并在解密之前检查它在接收方是否匹配。

您还可以使用 node.js 支持的 GCM 等身份验证模式。