如何将 IV 和密钥派生到 crypto.createCipheriv 以进行解密?
Posted
技术标签:
【中文标题】如何将 IV 和密钥派生到 crypto.createCipheriv 以进行解密?【英文标题】:How to derive IV and key to crypto.createCipheriv for decryption? 【发布时间】:2018-08-07 20:06:20 【问题描述】:我看到了其他关于为encryption 创建初始化向量 (IV) 的问题,似乎使用随机值是一种选择。但是,我需要生成 IV 进行解密,因此我必须使用基于某种盐对数据进行加密的相同 IV。
node.js 加密函数createDecipher 说:
crypto.createDecipher() 的实现使用 OpenSSL 函数 EVP_BytesToKey,摘要算法设置为 MD5, 一次迭代,没有盐。
为了向后兼容由其他软件加密的资产,我需要不同的迭代次数和我指定的盐。
继续阅读文档,它进一步说:
根据 OpenSSL 的建议,使用 PBKDF2 代替 EVP_BytesToKey 建议开发者派生出一个密钥和IV 自己使用 crypto.pbkdf2() 并使用 crypto.createDecipheriv() 创建 Decipher 对象。
好的,听起来不错。我需要解密的数据是使用 EVP_BytesToKey 加密以获取密钥和 IV,所以我需要与之兼容。
无论如何,crypto.pbkdf2 function 似乎接受了我需要的所有参数,但问题是,它似乎没有创建初始化向量。
需要兼容的解密对应的C代码如下:
// 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);
我尝试使用crypto.pbkdf2
来复制这种行为:
crypto.pbkdf2(password, salt, nrounds, 32, "md5", (err, derivedKey) =>
if (err) throw err
console.log(derivedKey.toString("hex"))
)
derivedKey
也与上面的 C 代码生成的密钥不匹配。我不确定这是否是预期的!我还尝试了 48 和 64 的密钥长度,但它们也没有生成与预期密钥和 IV 相似的任何内容。
给定正确的密码、盐和散列轮次,我如何生成相同的密钥和 IV 来解密?
【问题讨论】:
【参考方案1】:首先,你没有得到你想要的结果的原因是你的 C 代码确实使用了EVP_BytesToKey
,而你的 NodeJS 代码使用了 PBKDF2。我想你可能误解了 OpenSSL 的推荐。他们推荐 PBKDF2,不是作为产生相同结果的更好方法,而是作为解决问题的更好方法。 PBKDF2 只是一个更好的密钥派生函数,但它不会产生与EVP_BytesToKey
相同的结果。
此外,您最初处理 IV 代的方式非常糟糕。使用 KDF 生成密钥非常棒,做得很好。坦率地说,使用 KDF 生成 IV 是一个很糟糕的主意。您发现随机生成 IV 是一个好主意的初始读数是正确的。 所有 IV/nonce 应随机生成。总是。这里要记住的重要一点是静脉注射不是秘密。您可以公开传递。
大多数实现会随机生成一个 IV,然后将其作为密文的前缀。然后,在解密时,您可以简单地删除前 128 位 (AES) 的字节并将其用作 IV。这涵盖了您的所有基础,这意味着您不必从与关键材料相同的位置获取 IV(这很糟糕)。
有关详细信息,请参阅this GitHub repository 中的示例。我在下面包含了一个 NodeJS,这是 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() ]);
【讨论】:
嘿卢克,只是想澄清你的陈述“所有 IV/nonce 都应该随机生成。总是。”你的意思是总是在加密步骤,或者?我们不能用随机生成的 IV/nonce 解密某些东西,对吗?所以这就是为什么我们应该用密文发送盐和 IV/nonce 的原因。提前致谢! 嗨@Artiom,是的,这是正确的。用于加密的 IV/nonce 应始终随机生成。正如您正确指出的那样,在 解密 期间使用的 IV/nonce 应该与在加密期间使用的相同。以上是关于如何将 IV 和密钥派生到 crypto.createCipheriv 以进行解密?的主要内容,如果未能解决你的问题,请参考以下文章
Node.js 加密密钥和 iv 匹配 java SecretKeySpec / IvParameterSpec
如何使用 BouncyCastle 解密,使用 GCM Tag 的字符串,IV 字符串和密钥字符串,所有这些都是十六进制的?
使用 PBKDF2 和 AES256 进行加密和解密 - 需要实际示例 - 如何获取派生密钥