4. 密钥派生场景:密钥协商中的派生、加密中的派生、签名中的派生

密钥派生,说白了就是「一把钥匙开多把锁」。SM2 的私钥只有一个,但实际应用中我们需要多个密钥——加密密钥、签名密钥、会话密钥。总不能每次都拿主私钥去干活吧?那太危险了。

我个人习惯把派生场景分成三类:密钥协商中的派生、加密中的派生、签名中的派生。这三者的技术逻辑完全不同,但都依赖同一个核心工具——KDF(密钥派生函数)。

核心原则:主私钥永远不直接用于业务加密或签名。派生出来的子密钥用完即弃,或者按生命周期管理。

4.1 密钥协商中的派生

密钥协商,就是双方在公开信道上「聊」出一个共享密钥。SM2 的密钥协商协议基于 ECDH,但比标准 ECDH 多了一步——身份认证。

我记得第一次实现 SM2 密钥协商时,踩过一个坑:派生出来的密钥长度不对。后来才发现,KDF 的输入参数里漏掉了双方的 ID。嗯,这里要注意。

4.1.1 协商流程中的派生点

  • 步骤一:双方交换临时公钥 RA、RB
  • 步骤二:各自计算共享点 U = dA * RB(或 dB * RA)
  • 步骤三:将共享点的 x 坐标、双方 ID、公钥等信息拼接
  • 步骤四:调用 KDF 派生出会话密钥

你想想看,如果派生时不带身份信息,中间人就能伪造协商。SM2 协议强制要求把 IDA 和 IDB 塞进 KDF 输入,这就是防伪的关键。

实战建议:协商派生出的会话密钥通常只存活一次会话。我曾经在项目中看到有人把会话密钥存到数据库里重复用——这等于把临时房卡当永久钥匙用,风险极大。

4.1.2 派生参数对比

参数 说明 是否必选
共享点 x 坐标 ECDH 计算出的共享密钥材料 是
双方 ID 身份标识,防中间人 是
临时公钥 RA、RB 是
协商长度 期望输出的密钥比特数 是

4.2 加密中的派生

SM2 加密用的是「混合加密」——椭圆曲线加密 + 对称加密。公钥加密的只是对称密钥,真正的数据用 SM4 或 AES 加密。那对称密钥从哪来?派生来的。

加密场景的派生,我个人觉得是最容易出 bug 的地方。为什么?因为加密时派生一次,解密时还要派生一次,两次的输入参数必须完全一致。差一个字节,解密就炸了。

4.2.1 加密派生的标准流程

  1. 生成临时密钥对 (r, R)
  2. 计算共享点 P = r * QB(QB 是接收方公钥)
  3. 取 P 的 x 坐标作为 KDF 输入
  4. KDF 输出 t 字节的对称密钥
  5. 用对称密钥加密明文

避坑指南:我曾经遇到一个案例,加密方用了 128 位密钥,解密方却默认派生 256 位。结果前 128 位对得上,后 128 位全是乱码。KDF 的输出长度必须在两端显式约定,不能靠「默认」。

4.2.2 派生长度与安全等级

对称算法 推荐派生长度 说明
SM4 128 位 国密标准对称算法
AES-128 128 位 兼容国际标准
AES-256 256 位 更高安全等级

4.3 签名中的派生

签名场景的派生,和前面两个不太一样。签名不需要共享密钥,也不需要对称加密。那派生用在哪?

答案是:派生随机数 k。SM2 签名的安全性高度依赖 k 的随机性和不可预测性。如果 k 被预测或重复使用,私钥就能被算出来。嗯,这就是著名的「k 重用攻击」。

4.3.1 确定性派生 vs 随机派生

  • 随机派生:每次签名用真随机数生成器产生 k。优点是安全,缺点是依赖硬件熵源。
  • 确定性派生:用 HMAC_DRBG 或类似算法,从私钥和消息哈希派生出 k。优点是无需熵源,缺点是实现复杂。

我个人更倾向确定性派生。为什么?因为嵌入式设备上真随机数生成器经常「罢工」,而确定性派生只要种子不变,结果就是确定的——便于调试和复现。

关键点:签名中的派生不是为了生成「密钥」,而是为了生成「随机数」。但底层用的还是 KDF 或 DRBG 机制。

4.4 三种派生的核心差异

我把三种场景的差异整理成一张表,方便你对照。

场景 派生目标 输入材料 输出用途 安全风险
密钥协商 会话密钥 共享点 + 身份信息 对称加密通信 身份伪造、重放攻击
加密 对称密钥 共享点 x 坐标 加密明文数据 派生参数不一致
签名 随机数 k 私钥 + 消息哈希 生成签名值 k 重用导致私钥泄露

4.5 派生场景全景图

下面这张图展示了三种派生场景在 SM2 协议栈中的位置和关系。我建议你把它当作「路线图」——每次实现派生逻辑时,先看看自己站在哪个节点上。

SM2 密钥派生场景全景图 SM2 密钥派生场景全景图 SM2 私钥 密钥协商 加密 签名 KDF(共享点, ID) → 会话密钥 KDF(共享点 x) → 对称密钥 DRBG(私钥, 消息) → 随机数 k 会话密钥 对称密钥 签名值 核心原则:主私钥不直接参与业务,派生密钥按场景隔离

4.6 实战中的派生注意事项

最后,分享几个我在项目中积累的经验。这些坑,我基本都踩过一遍。

  • 参数一致性:加密和解密、协商双方,KDF 的输入参数必须完全一致。我曾经因为字符串编码不一致(UTF-8 vs GBK)导致派生结果不同,排查了两天。
  • 长度约定:派生长度必须在协议层显式约定。不要依赖「默认值」,不同库的默认值可能不同。
  • 随机数质量:签名场景的 k 值,如果使用随机派生,务必确认随机数生成器已经初始化。嵌入式设备上,熵池耗尽是个真实存在的风险。
  • 密钥生命周期:派生出的子密钥要有明确的过期时间。会话密钥用完即焚,对称密钥按数据生命周期管理。

我的习惯:每次实现派生逻辑时,先写一个「参数清单」,把 KDF 的所有输入参数列出来,两端逐一核对。这个习惯帮我避免了很多线上事故。