0

0

在 Node.js 中实现 HKDF-Expand 密钥扩展函数

聖光之護

聖光之護

发布时间:2025-09-22 10:48:08

|

537人浏览过

|

来源于php中文网

原创

在 node.js 中实现 hkdf-expand 密钥扩展函数

本文详细介绍了在 Node.js 环境下实现 HMAC-based Extract-and-Expand Key Derivation Function (HKDF) 的扩展(Expand)阶段。针对 Node.js crypto 模块不直接提供 HKDFExpand 的挑战,文章通过分析其算法原理,提供了一个基于 Bitwarden 源码的自定义实现方案,并结合 PBKDF2HMAC 演示了密钥派生的完整流程,旨在帮助开发者在 Node.js 中实现与 Python 等其他语言兼容的密钥扩展功能。

1. 理解 HKDF 与密钥派生

密钥派生函数 (Key Derivation Function, KDF) 的主要目的是从一个主密钥(或密码)派生出多个安全强度高、用途不同的子密钥。HKDF (HMAC-based Extract-and-Expand Key Derivation Function) 是一种广泛使用的 KDF,它由两个主要阶段组成:

  1. Extract(提取):从一个弱熵输入密钥(IKM, Input Key Material)和一个可选的盐值(salt)中提取出一个固定长度的伪随机密钥(PRK, Pseudo-Random Key)。这个阶段通常使用 HMAC-SHA256 等算法。
  2. Expand(扩展):从 PRK 派生出所需长度的输出密钥材料(OKM, Output Key Material)。这个阶段允许派生出多个不同用途的子密钥,每个子密钥都可以通过一个“信息”(info)字符串进行上下文绑定,确保其唯一性。

在 Bitwarden 等密码管理器中,通常会先使用 PBKDF2HMAC 等算法从用户密码派生出一个主密钥,然后将此主密钥作为 HKDF 的 PRK,通过 HKDF-Expand 派生出用于加密、解密等操作的实际工作密钥。

2. Node.js 中实现 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 的方法是错误的。

3. HKDF-Expand 算法原理

HKDF-Expand 的核心在于迭代地计算 HMAC,并拼接上下文信息和计数器。其算法步骤如下:

人民网AIGC-X
人民网AIGC-X

国内科研机构联合推出的AI生成内容检测工具

下载

给定:

  • PRK (Pseudo-Random Key):伪随机密钥,长度至少为哈希输出长度。
  • info:可选的上下文信息字符串。
  • L:所需输出密钥材料的字节长度。
  • Hash:HMAC 使用的哈希函数(例如 SHA256),其输出长度为 hashLen。

步骤:

  1. 计算 N = ceil(L / hashLen),即需要进行 HMAC 迭代的次数。
  2. 初始化 T(0) 为空字节序列。
  3. 对于 i 从 1 到 N:
    • T(i) = HMAC(PRK, T(i-1) || info || L_byte)
      • || 表示字节序列拼接。
      • L_byte 是一个单字节,表示当前的迭代计数 i (例如,0x01, 0x02, ...)。
  4. 最终的 OKM 是 T(1) || T(2) || ... || T(N) 的前 L 个字节。

4. 在 Node.js 中实现自定义 HKDF-Expand 函数

根据 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);
}

5. 完整示例:结合 PBKDF2HMAC 和 HKDF-Expand

现在,我们可以将 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 的行为。

6. 注意事项

  • Buffer 与 Uint8Array 的转换:在 Node.js 中,Buffer 和 Uint8Array 都是处理二进制数据的重要类型。在加密操作中,经常需要在这两者之间进行转换。Buffer.from(uint8array) 和 new Uint8Array(buffer) 是常见的转换方式。本例中,为了与 crypto 模块的 Buffer 接口兼容,并在内部处理中方便地进行字节拼接,使用了两者结合的方式。
  • 安全性
    • 盐值 (Salt):在实际应用中,盐值必须是随机生成且足够长的,并且对于每个用户或每个密钥都应该是唯一的,不能使用固定的字符串如 'salt'。
    • 迭代次数 (Iterations):PBKDF2 的迭代次数应足够大,以抵御暴力破解攻击。随着计算能力提升,推荐的迭代次数也在增加。
    • 密钥长度:派生出的密钥长度应根据加密算法的要求选择,通常为 16 字节(AES-128)、24 字节(AES-192)或 32 字节(AES-256)。
  • 上下文信息 (Info):HKDF-Expand 的 info 参数非常重要,它用于将派生出的密钥与特定的上下文或用途绑定。不同的 info 值将派生出不同的密钥,即使 PRK 相同。这有助于防止密钥重用,并提高安全性。
  • 性能:对于性能敏感的应用,尤其是需要大量密钥派生操作的场景,应评估自定义实现的性能。Node.js 的 crypto 模块底层是 C++ 实现,性能通常较高,但手动循环和内存操作仍需注意。
  • 库的替代方案:虽然 Node.js crypto 模块没有直接提供 HKDF-Expand,但社区中可能存在封装好的第三方库,它们可能提供了更简洁的 API 或更全面的功能。在生产环境中,优先考虑使用经过充分审计和测试的第三方库,而不是自行实现复杂加密算法。

7. 总结

在 Node.js 中实现 HKDF-Expand 密钥扩展功能,虽然 crypto 模块未直接提供 API,但通过深入理解 HKDF 的算法原理,并利用 crypto.createHmac 等基本原语,我们可以构建一个功能完整且与其他语言实现兼容的自定义函数。本文提供的实现方案,源自对实际应用(如 Bitwarden)的分析,展示了如何从 PBKDF2HMAC 派生主密钥,再通过自定义的 HKDF-Expand 派生出最终的工作密钥。在实际应用中,务必关注安全性最佳实践,确保密钥派生过程的健壮性和安全性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1501

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

624

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

633

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

589

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

172

2025.07.29

c++字符串相关教程
c++字符串相关教程

本专题整合了c++字符串相关教程,阅读专题下面的文章了解更多详细内容。

83

2025.08.07

clawdbot ai使用教程 保姆级clawdbot部署安装手册
clawdbot ai使用教程 保姆级clawdbot部署安装手册

Clawdbot是一个“有灵魂”的AI助手,可以帮用户清空收件箱、发送电子邮件、管理日历、办理航班值机等等,并且可以接入用户常用的任何聊天APP,所有的操作均可通过WhatsApp、Telegram等平台完成,用户只需通过对话,就能操控设备自动执行各类任务。

16

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 3.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号