摘要

  • HKDF 的盐通常不是秘密。它可以帮助不同用途彼此独立、加强提取的安全论证,但不能增加输入本来没有的熵,也不能证明输入经过认证。
  • info 属于扩展阶段,负责把输出绑定到协议、版本、角色或用途。只有同时保留输入来源、盐的来历、上下文字节和输出职责,派生结果才是一条可审计的证据链。

“盐已公开”并不是故障结论

设想一次跨团队告警:抓包里能看到双方使用的盐,应用日志又显示 HKDF 计算成功。一方认为盐暴露导致密钥泄露,另一方则用“双方算出相同结果”证明实现无误。两种判断都跨过了证据边界。

在 RFC 5869 中,salt 被明确写成可选的、非秘密的随机值;没有提供时,HKDF 使用与哈希输出等长的全零字符串。旁观者看见盐并不等于看见密钥。与此同时,双方得到相同输出只说明它们向同一算法交付了等价输入,并不能说明初始密钥材料足够不可预测、盐没有受攻击者操纵,或输出被分配给了正确用途。

这正是 Hugo Krawczyk 的工作适合作为人物切口的原因。他与 Pasi Eronen 共同撰写 RFC 5869,而不是独自拥有这项标准;更早的论文给出了 HKDF 的形式化动机。IETF 的作者记录也把 HMAC 的 RFC 与他的工作联系起来。ACM 在 2025 年的奖项记录中表彰了他对安全通信理论基础及实际协议的贡献。比荣誉更重要的是一种工程纪律:不要把“生成一串看似随机的字节”当成密钥生命周期的全部。

提取只能整理已有的熵

HKDF 先执行提取:

PRK = HMAC-Hash(salt, IKM)

IKM 是初始密钥材料,PRK 是固定长度的伪随机密钥。提取阶段适合处理分布未必均匀、攻击者可能知道一部分结构的输入,把其中已有的不确定性集中成适合后续 HMAC 使用的值。但“集中”不是“创造”。如果人类密码只有很小的候选空间,攻击者仍可枚举每个候选并执行同样的快速计算。

是否可以跳过提取,取决于 IKM 的性质,不取决于开发者想省一次函数调用。已经是高质量伪随机密钥的输入,在某些场景可以直接扩展;Diffie–Hellman 共享值却不应被当作均匀的 HMAC 密钥,RFC 5869 因此建议不要跳过提取。NIST SP 800-56C Rev. 2 也把提取、扩展及先提取后扩展作为密钥建立中的明确方法。阶段名称不是代码风格,而是在回答:我们对源材料究竟已经证明了什么?

公开的盐为何仍然有用

盐的作用不来自保密。合适的盐能够支持“源无关”提取,改善不同哈希用途之间的独立性,并加强设计所依赖的分析条件。协议可以从已认证的公开随机数构造盐,也可以按安全分析使用固定值;甚至质量有限的盐也可能比完全没有更好。

但“非秘密”绝不等于“攻击者可随意挑选”。RFC 5869 要求盐与 IKM 独立。在依赖双方随机数的密钥交换里,还要先确认这些随机数来自合法参与方,而不是让攻击者塑造提取输入。因而,运维台账不能只记一个布尔字段 salt_present=true。至少应区分:未提供而采用协议默认值、协议固定盐、经过认证的新鲜公开盐,以及来源可能受攻击者影响的盐。

RFC 8188 的 HTTP 加密内容编码把区别写得很清楚:传输中的盐进入 HKDF-Extract,固定的内容编码字符串则作为 info 进入扩展。它们可以同时公开,却承担不同任务。可见性不是分类标准,所处阶段和安全假设才是。

密码里的“盐”是另一套控制组合

密码系统也使用盐,因此最容易发生概念偷换。密码盐主要阻止大规模预计算,并让相同密码产生不同结果;与此同时,低熵密码需要有意设置时间或内存成本,使每次离线猜测付出代价。

HKDF 没有工作因子,也没有内存困难阶段。RFC 5869 明确提醒:Extract 只能集中已有熵,不能放大熵;HKDF 本身没有密码派生所需的减速机制。PBKDF2 把迭代次数列为参数,Argon2 则把内存、轮次和并行度纳入设计。它们也无法把糟糕密码变成高熵秘密,却能改变批量猜测的经济性。

所以,“我们用了盐”不是充分的审计答案。还要追问:哪个构造消费了盐?输入是随机密钥、共享秘密还是人类密码?攻击者尝试一次猜测的成本是多少?盐需要唯一、随机、独立还是已认证?同一个名词在不同构造中并不授予相同保证。

info 让密钥获得用途

提取之后,HKDF-Expand 接受 PRK、info 和输出长度。info 可以编码协议号、算法、身份、角色或输出长度,用来防止同一 IKM 在不同上下文中产生无法区分的材料。它应独立于 IKM 的具体值,且不能随意搬到 Extract 中冒充盐。

这也解释了为什么直接把 PRK 当最终密钥并不稳妥。即使所需长度不超过一个哈希输出,RFC 5869 仍不建议这样做,因为它绕过了 info。PRK 表示“可以继续派生的中间秘密”,而不是“已经知道自己负责哪项操作的密钥”。

TLS 1.3 把这种职责做成了字节级协议。HKDF-Expand-Label 给标签加上 tls13 前缀,并可把握手转录哈希纳入上下文;finished、key、iv 等不同标签进入实际计算。QUIC 又以 quic key、quic iv、quic hp 区分数据加密、初始化向量和包头保护,并明确用这些标签隔离 QUIC 与 TLS 的用途。

HPKE 进一步把 HPKE-v1、密码套件标识和用途标签写入 LabeledExtract/LabeledExpand,以免相同共享秘密跨版本或跨方案串用。MLS 使用自己的 MLS 1.0 前缀和分层密钥日程,为群组的每个 epoch 派生不同秘密。标签因此不是供人阅读的注释;只有编码无歧义、双方实现一致、注册表和版本规则受到校验时,它才真正成为密码学域的一部分。

派生记录需要四张凭证

第一张是源材料凭证。记录 IKM 来自经过认证的 Diffie–Hellman 交换、随机密钥、预共享秘密、密码,还是上一层派生。字节长度相同,不代表熵和攻击面相同。

第二张是盐凭证。保留生成规则、确切版本、认证状态、复用策略以及与 IKM 的独立性依据。公开只描述保密等级,不描述来源可信度。

第三张是上下文凭证。保存真正进入 Expand 的编码字节:协议和版本前缀、标签、密码套件、方向或角色、转录摘要和长度。仅保存界面显示的“traffic key”会掩盖拼接歧义、字符编码差异或遗漏字段。

第四张是输出托管凭证。说明产物究竟是密钥、IV、导出秘密、恢复秘密还是 PRK,由谁使用、允许哪些操作、属于哪个 epoch、何时删除。正确派生的密钥如果搭配了错误的 nonce 规则,或在轮换后仍长期存活,仍是安全故障。

四张凭证合起来,才不会把确定性算法误当成安全裁判。它们既能解释一次不一致,也能揭示更危险的情形:所有实现都一致地做错了同一件事。

来源