3. 密钥派生原理:KDF 的定义、规范与安全要求
密钥派生函数,简称 KDF,是 SM2 算法体系中一个容易被忽视、但极其关键的环节。说白了,它的任务就是:把一份共享的秘密,变成你真正能用的密钥。
我早年做嵌入式安全方案时,就踩过 KDF 的坑。当时觉得“不就是算个哈希嘛”,结果产品在互操作性测试中直接翻车。嗯,从那以后,我对 KDF 的每一个字节都不敢马虎。
3.1 什么是 KDF?为什么需要它?
SM2 密钥交换协议完成后,通信双方会得到一个共享的椭圆曲线点 (x, y)。这个点是一个大整数对,长度通常在 512 位以上。但实际加密、签名或会话保护,需要的往往是 128 位、192 位或 256 位的对称密钥。
你想想看,直接把坐标 x 截断当密钥用?不行。一来长度不匹配,二来椭圆曲线点的坐标并不是均匀随机的,直接截断会有安全风险。
KDF 的作用就是:将输入的共享秘密(以及可能的上下文信息),通过一个确定的、安全的算法,扩展或压缩成任意长度的密钥材料。
核心原则:KDF 必须保证输出在计算上不可与随机数区分。哪怕输入只差一个比特,输出也必须完全不一样。
3.2 SM2 KDF 算法规范
SM2 使用的 KDF 定义在 GB/T 32918.1-2016 中。它基于 SM3 哈希函数,采用了一种经典的“计数器模式”构造。
我个人习惯把它的流程总结为三步:
- 初始化:确定输出密钥长度
klen(比特数),计算需要的迭代次数n = ceil(klen / v),其中v是 SM3 的输出长度(256 位)。 - 迭代计算:对
i = 1到n,计算Ha(i) = SM3(Z || ct(i))。其中Z是输入的共享秘密(通常包含坐标x和y的拼接),ct(i)是 32 位大端表示的计数器。 - 截断输出:将
Ha(1) || Ha(2) || ... || Ha(n)拼接,取前klen位作为最终密钥。
用代码表示会更直观:
// SM2 KDF 核心实现(伪代码)
// 输入:Z - 共享秘密(字节串)
// klen - 期望输出的密钥长度(比特)
// 输出:K - 派生密钥(字节串)
function SM2_KDF(Z, klen):
v = 256 // SM3 输出长度(比特)
n = ceil(klen / v) // 迭代次数
K = empty_bytes
for i = 1 to n:
ct = int_to_32bit_big_endian(i)
Ha = SM3(Z || ct)
K = K || Ha
// 截断到 klen 比特
return K[0 : klen/8]
我的一点经验:计数器 ct(i) 的编码方式一定要和协议严格对齐。我曾经见过一个项目,因为大小端搞反了,两边的派生密钥始终对不上。排查了整整两天,最后发现是字节序的问题。
3.3 派生场景:KDF 在 TEE 中的典型用法
在 TEE 环境中,SM2 KDF 主要出现在以下几个场景:
| 场景 | 输入 Z | 输出用途 | 安全要求 |
|---|---|---|---|
| 密钥交换 | 共享点 (x, y) | 会话加密密钥 | 需包含双方身份标识 |
| 加密派生 | 临时公钥 + 共享秘密 | 对称加密密钥 | 需绑定消息上下文 |
| 身份认证 | 长期密钥派生值 | 认证令牌 | 需加入时间戳防重放 |
| 密钥封装 | 封装的密钥材料 | 数据保护密钥 | 需验证封装完整性 |
在 TEE 里,KDF 的执行环境很特殊。所有中间计算结果——包括计数器、哈希中间值、最终密钥——都必须在安全内存中处理,不能泄露到普通世界。
3.4 安全要求与避坑指南
KDF 看起来简单,但安全细节很多。我总结了几个必须注意的点:
- 输入唯一性:每次派生使用的
Z必须不同。如果两次派生用了相同的Z,攻击者可以通过比较输出获得信息。 - 上下文绑定:在 TEE 中,我建议把会话 ID、角色标识、算法标识都拼进
Z里。这样即使共享秘密相同,不同上下文的派生密钥也不同。 - 侧信道防护:KDF 的计算路径不能依赖输入值。SM3 的循环次数、内存访问模式都应该是固定的。
- 密钥生命周期:派生出的密钥用完后,必须立即擦除。在 TEE 里,我习惯用专门的
memset_secure函数,防止编译器优化掉清零操作。
我曾经踩过的坑:在一个 IoT 项目中,KDF 的输入 Z 只包含了共享点的 x 坐标,没有包含 y。结果攻击者可以通过选择不同的 y 值(对应同一个 x)来构造多个有效共享秘密,从而获得多个不同的派生密钥。这直接破坏了密钥的唯一性。
正确的做法是:始终使用完整的 (x, y) 坐标对作为 KDF 输入。
3.5 KDF 流程全景图
下面这张图展示了 SM2 KDF 的完整流程,以及它在 TEE 中的安全边界:
3.6 小结
KDF 是 SM2 密钥派生中的“翻译官”——把椭圆曲线上的共享秘密,翻译成对称密码能用的密钥。SM2 规范选择了 SM3 加计数器模式,这个设计很务实:既利用了 SM3 的伪随机性,又通过计数器保证了输出长度可扩展。
在 TEE 里实现 KDF,技术本身不难。真正的挑战在于:输入要绑上下文,计算要防侧信道,输出要及时擦除。这三个点,任何一个出问题,整个密钥体系都会崩塌。