
密钥派生函数 (Key Derivation Function, KDF) 的主要目的是从一个主密钥(或密码)派生出多个安全强度高、用途不同的子密钥。HKDF (HMAC-based Extract-and-Expand Key Derivation Function) 是一种广泛使用的 KDF,它由两个主要阶段组成:
在 Bitwarden 等密码管理器中,通常会先使用 PBKDF2HMAC 等算法从用户密码派生出一个主密钥,然后将此主密钥作为 HKDF 的 PRK,通过 HKDF-Expand 派生出用于加密、解密等操作的实际工作密钥。
Node.js 的 crypto 模块提供了 HMAC、PBKDF2 等核心加密原语,但并没有直接提供一个与 cryptography.hazmat.primitives.kdf.hkdf.HKDFExpand 功能完全对应的 API。这导致在尝试从 Python 等其他语言移植使用 HKDF-Expand 的密钥派生逻辑时,开发者可能会遇到不一致的结果。
例如,直接使用 crypto.createHmac 尝试模拟 HKDF-Expand 是不正确的,因为 HKDF-Expand 包含了一个迭代过程和特定的数据拼接逻辑。
不正确的尝试示例:
import crypto from 'crypto';
const salt = 'salt';
const iterations = 100000;
const password = '123';
const info = 'enc'; // 对应 HKDF-Expand 的 info 参数
// 1. PBKDF2HMAC 派生主密钥 (masterKey)
const masterKey = crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha256');
console.log('Node.js Master Key:', masterKey.toString('hex')); // 与 Python 结果一致
// 2. 尝试使用 createHmac 模拟 HKDF-Expand
// 这种方式是错误的,因为它不符合 HKDF-Expand 的算法规范
const stretchedKeyAttempt = crypto.createHmac('sha256', info).update(masterKey).digest();
console.log('Node.js Stretched Key (Incorrect Attempt):', stretchedKeyAttempt.toString('hex'));与 Python cryptography 库的正确 HKDF-Expand 结果进行对比,可以发现 stretchedKeyAttempt 的结果是不同的,这验证了直接使用 createHmac 的方法是错误的。
HKDF-Expand 的核心在于迭代地计算 HMAC,并拼接上下文信息和计数器。其算法步骤如下:
给定:
步骤:
根据 HKDF-Expand 的算法原理,我们可以实现一个兼容的自定义函数。以下是基于 Bitwarden 源码的实现,它利用 Node.js crypto 模块的 createHmac 功能来执行 HMAC 运算。
import crypto from 'crypto';
/**
* 内部 HMAC 辅助函数
* @param data 要进行 HMAC 计算的数据
* @param key HMAC 密钥
* @param algorithm 哈希算法(例如 'sha256')
* @returns HMAC 结果 Buffer
*/
function hmac(data: Uint8Array, key: Buffer, algorithm: string): Buffer {
return crypto.createHmac(algorithm, key).update(data).digest();
}
/**
* 实现 HKDF-Expand 密钥扩展函数
*
* @param prk 伪随机密钥 (Pseudo-Random Key),Buffer 类型
* @param info 上下文信息字符串
* @param outputByteSize 所需输出密钥的字节长度
* @returns 派生出的输出密钥材料 (Output Key Material) Buffer
*/
function hkdfExpand(
prk: Buffer,
info: string,
outputByteSize: number
): Buffer {
const algorithm = "sha256"; // 使用 SHA256 哈希算法
const hashLen = 32; // SHA256 的输出长度为 32 字节
// 确保 PRK 长度足够
const prkArr = new Uint8Array(prk);
if (prkArr.length < hashLen) {
throw new Error("prk is too small.");
}
// 将 info 字符串转换为 Uint8Array
const infoBuf = Buffer.from(info);
const infoArr = new Uint8Array(infoBuf);
let runningOkmLength = 0; // 当前已生成的 OKM 长度
let previousT = new Uint8Array(0); // T(i-1)
// 计算需要迭代的次数 N
const n = Math.ceil(outputByteSize / hashLen);
// 预分配 OKM 存储空间
const okm = new Uint8Array(n * hashLen);
for (let i = 0; i < n; i++) {
// 构建 T(i) 的输入:T(i-1) || info || (i+1)
const t = new Uint8Array(previousT.length + infoArr.length + 1);
t.set(previousT);
t.set(infoArr, previousT.length);
t.set([i + 1], t.length - 1); // 计数器从 1 开始
// 计算 HMAC,得到 T(i)
previousT = new Uint8Array(hmac(t, prk, algorithm));
// 将 T(i) 复制到 OKM 缓冲区
okm.set(previousT, runningOkmLength);
runningOkmLength += previousT.length;
// 如果已生成的 OKM 长度达到或超过所需长度,则停止
if (runningOkmLength >= outputByteSize) {
break;
}
}
// 返回 OKM 的前 outputByteSize 字节
return Buffer.from(okm.slice(0, outputByteSize).buffer);
}现在,我们可以将 PBKDF2HMAC 密钥派生与自定义的 hkdfExpand 函数结合起来,实现完整的密钥派生过程。
import crypto from 'crypto';
// 假设上述 hkdfExpand 和 hmac 函数已定义或导入
// ... (hkdfExpand 和 hmac 函数代码如上所示) ...
const salt = 'salt';
const iterations = 100000;
const password = '123';
const info = 'enc'; // HKDF-Expand 的上下文信息
const outputKeyLength = 32; // 期望派生出的密钥长度
// 1. 使用 PBKDF2HMAC 从密码派生主密钥 (PRK)
const masterKey = crypto.pbkdf2Sync(
password,
salt,
iterations,
outputKeyLength, // 这里通常是 32 字节,作为 HKDF 的 PRK
'sha256'
);
console.log('Node.js Master Key (PBKDF2):', masterKey.toString('hex'));
// 2. 使用自定义的 hkdfExpand 函数从 masterKey 派生最终的工作密钥
const stretchedKey = hkdfExpand(masterKey, info, outputKeyLength);
console.log('Node.js Stretched Key (HKDF-Expand):', stretchedKey.toString('hex'));
// Python 对应结果 (仅供参考,应与上述 Node.js 结果一致)
// master_key.hex() # 5bb4...5990
// stretched.hex() # 5bf9...473b通过运行上述代码,你会发现 Node.js 输出的 stretchedKey 与 Python 中 HKDFExpand 派生出的密钥完全一致。这表明自定义的 hkdfExpand 函数成功地模拟了 HKDF-Expand 的行为。
在 Node.js 中实现 HKDF-Expand 密钥扩展功能,虽然 crypto 模块未直接提供 API,但通过深入理解 HKDF 的算法原理,并利用 crypto.createHmac 等基本原语,我们可以构建一个功能完整且与其他语言实现兼容的自定义函数。本文提供的实现方案,源自对实际应用(如 Bitwarden)的分析,展示了如何从 PBKDF2HMAC 派生主密钥,再通过自定义的 HKDF-Expand 派生出最终的工作密钥。在实际应用中,务必关注安全性最佳实践,确保密钥派生过程的健壮性和安全性。
以上就是在 Node.js 中实现 HKDF-Expand 密钥扩展函数的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号