HKDF-SHA256 密钥派生实战:salt、info 与 AES-256 会话密钥隔离
来源:17golang原创
时间:2026-08-16 20:06:20 147浏览 收藏
服务端准备给每个会话派生一把临时密钥时,最容易踩的坑是直接把原始密钥材料截取几段用,或是直接拿一次 SHA-256 的输出当所有场景的子密钥。Java 25 提供的密钥派生函数(KDF)API 把这件事做成了标准的 Extract/Expand 流程,HKDF-SHA256 还能通过 salt 和 info 把密钥用途直接绑定到协议上下文,避免不同场景密钥混用。
- Java 25 通过
KDF.getInstance("HKDF-SHA256")获取 HKDF 实现,KDF 只负责密钥派生,不承担加密或者随机数生成的职责。 salt作用于 Extract 阶段,info作用于 Expand 阶段;把会话 ID、租户 ID 和业务用途写进 info 字段,就能避免不同用途复用同一派生结果。- 只有原始密钥材料不适合直接作为业务密钥的场景才需要用 KDF,KDF 的输入本身必须先有足够的熵,来自安全可信的来源。
- 验收派生逻辑时要固定算法、输出长度、salt、info 和输入材料,验证重复调用派生结果完全一致、上下文变化时派生结果必然不同,还要覆盖非法长度的输入场景。
先把 KDF 和加密、哈希分开
KDF 的核心职责是:拿到一段已有的安全密钥材料和上下文信息,输出指定长度、绑定指定用途的派生子密钥。它不会输出密文,也不会帮你生成高质量的随机种子。
| 能力 | 输入 | 输出 | 不该拿来做什么 |
|---|---|---|---|
| KDF | 密钥材料、salt、info、输出长度 | 用途隔离的子密钥 | 直接用来加密业务数据 |
| 哈希 | 任意消息内容 | 固定长度摘要 | 替代带上下文绑定的密钥派生 |
| AEAD | 密钥、nonce、明文、AAD | 密文与认证标签 | 直接从低熵口令生成安全密钥 |
| SecureRandom | 系统级熵源 | 随机字节流 | 把已有固定密钥材料生成多用途子密钥 |
Java 25 由 JEP 510 引入标准 KDF 服务接口,具体算法由对应的加密提供者实现。下面示例用的是 HKDF-SHA256,生产环境使用前要先确认运行环境已经装好对应加密提供者,走完项目要求的密码学方案评审流程。

用 HKDF-SHA256 派生一把会话密钥
Java 25 API 的最小调用逻辑可以围绕 KDF、HKDFParameterSpec 和 SecretKey 这几个核心对象组织。示例里的输入只是用来演示 API 调用方式,真实线上服务必须从密钥管理系统或者安全握手协议获取合法密钥材料,绝对不能把口令字面量直接当成生产密钥来用。
import javax.crypto.KDF;
import javax.crypto.SecretKey;
import javax.crypto.spec.HKDFParameterSpec;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
public class SessionKeyDemo {
public static void main(String[] args) throws Exception {
byte[] keyMaterial = "demo-key-material-with-enough-entropy"
.getBytes(StandardCharsets.UTF_8);
byte[] salt = HexFormat.of().parseHex("00112233445566778899aabbccddeeff");
byte[] info = "checkout/session/v1/tenant-17".getBytes(StandardCharsets.UTF_8);
KDF kdf = KDF.getInstance("HKDF-SHA256");
HKDFParameterSpec params = HKDFParameterSpec.ofExtract()
.addIKM(keyMaterial)
.addSalt(salt)
.thenExpand(info, 32);
SecretKey sessionKey = kdf.deriveKey("AES", params);
System.out.println(HexFormat.of().formatHex(sessionKey.getEncoded()));
}
}
代码编译时需要指定 Java 25 作为目标版本:
javac --release 25 SessionKeyDemo.java java SessionKeyDemo
输出的 32 字节内容可以直接作为 AES-256 的密钥输入,但真正做加密操作时还是要由 AEAD 算法独立生成并管理 nonce,同时把需要的附加认证数据传给加密接口,KDF 的输出本身并不是已经加密完成的业务数据。
salt 和 info 应该分别绑定什么
HKDF 的两个阶段解决的是完全不同的问题。Extract 阶段把输入密钥材料和 salt 汇聚成统一的伪随机密钥,Expand 阶段再根据 info 字段和要求的长度,派生出绑定具体用途的结果。实际开发里可以按这个逻辑分配职责:
- salt:由协议或者会话生成的非秘密随机值,作用是改变 Extract 阶段的输出结果;它可以和握手记录一起存储,不能用固定字符串直接冒充随机 salt。
- info:描述派生用途的上下文标识,例如
checkout/session/v1/tenant-17、协议版本号、租户 ID 和通信方向。 - 输出长度:由下游使用的算法决定,AES-256 对应 32 字节密钥,HMAC-SHA256 的密钥长度按照协议约定取值,不要随意对结果做截取或者补零操作。
同一份输入密钥材料只要更换 info 内容,就必须得到不同的派生结果。这样“会话数据加密”和“回调请求签名”就能拿到完全独立的密钥,就算某一把密钥泄露,影响范围也只会限制在对应场景下,不会波及其他业务逻辑。

用测试锁住重复性、隔离性和边界条件
KDF 的测试不能只写断言“返回了 32 个字节”这种弱校验,至少要覆盖三类核心特性:
static byte[] derive(byte[] material, byte[] salt, String purpose)
throws Exception {
KDF kdf = KDF.getInstance("HKDF-SHA256");
HKDFParameterSpec params = HKDFParameterSpec.ofExtract()
.addIKM(material)
.addSalt(salt)
.thenExpand(purpose.getBytes(StandardCharsets.UTF_8), 32);
return kdf.deriveData(params);
}
// 同样的输入和上下文,应得到同样的 32 字节结果
assertArrayEquals(first, derive(material, salt, "checkout/session/v1"));
// 用途变化后,不应继续复用同一派生结果
assertFalse(Arrays.equals(first, derive(material, salt, "webhook/sign/v1")));
assertEquals(32, first.length);
测试用例可以用固定公开向量,但绝对不能把测试向量、演示用默认 salt 或者示例口令带进生产配置。还要补充一条提供者兼容性检查逻辑:服务启动时主动获取 HKDF-SHA256,如果对应算法不可用就直接快速失败抛出告警,不要悄悄回退到自定义哈希拼接的野路子实现。
几个容易误用的地方
- 把 KDF 当成密码存储方案。用户口令属于低熵输入,应该用专门面向密码存储的慢哈希方案,配置合适的工作因子;KDF 不是口令哈希的替代方案。
- 忽略用途和版本信息。info 只写
session实在太宽泛,至少要把协议版本、通信方向或者具体业务用途放到 info 里做区分。 - 把 salt 当成秘密凭据。salt 的作用是改变派生过程的输出,本身不需要保密;它可以跟着协议元数据一起传输,只要保证生成逻辑和关联关系可靠就行。
- 忽略输出长度的约束。派生结果的输出长度必须由下游算法和协议明确定义,调用方不能拿到一段任意长度的字节后凭自己的感觉随便截取。
- 没有回归固定测试向量。算法名称、加密提供者、salt、info 或者输出长度发生变更时,固定测试向量能第一时间发现协议不兼容问题。
上线前的 Java 25 KDF 检查表
- 确认构建环境和线上运行环境都是支持 JEP 510 的 Java 25 版本,记录对应加密提供者的来源。
- 明确密钥材料的来源、存储周期和访问权限,禁止把低熵口令直接当做 KDF 的输入。
- 给每一类不同的业务用途定义独立的 info 前缀和对应的协议版本号。
- 对 salt、info、派生长度和算法名做全量参数校验,非法输入直接返回错误,不要向下传递。
- 准备好固定向量用例和上下文隔离测试用例,升级 JDK 或者加密提供者之后重新跑一遍所有用例。
如果项目只是需要一把全新的随机 AES 密钥,直接用 KeyGenerator 生成会更简单;只有当你有一份已有的密钥材料,需要按照协议派生出多把用途隔离的子密钥时,KDF 才能发挥它的价值。
相关问题
Java 25 KDF API 支持哪些算法?
JEP 510 只定义了标准服务接口,实际可用的算法列表取决于 JDK 安装的加密提供者。本文示例用的是 HKDF-SHA256,部署前要在目标运行环境里提前查询并验证算法名称,不要默认所有提供者都提供完全一致的实现。
HKDF 的 salt 必须保密吗?
通常不需要保密。salt 主要作用于 Extract 阶段,核心要求是生成方式安全、关联关系正确、和协议约定保持一致;它不能代替密钥材料本身,也不能单独承担认证的职责。
info 可以为空吗?
底层 API 可能允许传入空的 info 字段,但业务协议层面通常不应该省略用途上下文。把用途、版本、租户或者通信方向编码到 info 里,能大幅降低不同场景误用同一派生结果的风险。
KDF 输出可以直接当登录密码吗?
不建议这么做。登录密码属于低熵、容易被暴力猜测的输入,应该用专门的密码存储方案处理;KDF 更适合处理本身就有足够高熵的密钥材料,完成协议层面的派生操作。
把派生规则当成协议的一部分
Java 25 的 KDF API 只解决了“怎么调用标准派生服务”的问题,不会替团队决定密钥生命周期管理、协议字段定义和权限控制模型。把派生算法、salt 规则、info 字段规范、输出长度和加密提供者要求都写进正式协议文档和测试向量里,业务代码只负责传入经过校验的合法参数,后续版本升级时才不会因为一行 API 替换就意外改变密钥的语义。
-
413 收藏
-
144 收藏
-
239 收藏
-
126 收藏
-
文章 · java教程 | 2个月前 | 并发编程 · Spring Boot · 生产实践 · Java教程 · 线程池隔离 · java 并发编程 线程池 spring boot completablefuture191 收藏
-
文章 · java教程 | 5小时前 | 反序列化 · Java教程 · 安全编程 · Java IO · java Java安全 ObjectInputFilter 反序列化过滤 Serialization Filter257 收藏
-
文章 · java教程 | 5小时前 | 反序列化 · Java教程 · 安全编程 · Java IO · java Java安全 ObjectInputFilter 反序列化过滤 Serialization Filter186 收藏
-
143 收藏
-
270 收藏
-
文章 · java教程 | 8小时前 | Java教程 · Java 24 · 字节码 · Class-File API · JVM工具 · 字节码 Java 24 Class-File API ClassModel ASM替代 java.lang.classfile352 收藏
-
428 收藏
-
文章 · java教程 | 9小时前 | 并发编程 · Java教程 · 虚拟线程 · threadlocal 请求上下文 Java 25 StructuredTaskScope ScopedValue391 收藏
-
296 收藏
-
文章 · java教程 | 1星期前 | Stream · Java教程 · 性能边界 · mapMulti · flatMap · Java mapMulti Stream flatMap Java Stream 条件展开 Java 16250 收藏
-
150 收藏
-
125 收藏
-
242 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习