第4节:SM2椭圆曲线公钥密码算法(签名、加密、密钥交换)
SM2 算法,说白了就是国密体系里的公钥密码核心。椭圆曲线这一套,国际上有 ECDSA、ECIES,我们国家有自己的曲线参数和签名/加密/密钥交换标准。做 HSM 硬件实现,这块逃不掉,而且坑不少。
我个人习惯把 SM2 拆成三层来理解:底层是有限域运算(模乘、模逆、点加、点倍),中间层是椭圆曲线上的标量乘法,上层才是签名、加密、密钥交换协议。硬件 HSM 里,最吃资源的是点乘,特别是固定基点乘和随机点乘的并行优化。
4.1 SM2 数字签名算法
签名算法,我遇到过不少实现细节问题。SM2 签名流程跟 ECDSA 很像,但有个关键差别——它引入了用户 ID 和杂凑值 Z 的计算。这个 Z 值把曲线参数、公钥、用户 ID 全部绑在一起,防止中间人替换公钥。
签名生成步骤(简化版):
- 计算 Z = SM3(ENTL || ID || a || b || xG || yG || xA || yA)
- 计算 M' = Z || M,然后 e = SM3(M')
- 产生随机数 k ∈ [1, n-1]
- 计算点 (x1, y1) = k * G
- 计算 r = (e + x1) mod n,若 r=0 或 r+k=n 则重选 k
- 计算 s = ((1 + dA)⁻¹ * (k - r * dA)) mod n,若 s=0 则重选 k
- 输出签名 (r, s)
验证流程更直接:先同样算 Z 和 e,然后判断 r 和 s 是否在 [1, n-1] 内,再算 t = (r + s) mod n,若 t=0 则拒绝;计算点 (x1, y1) = s * G + t * PA,最后检查 (e + x1) mod n 是否等于 r。嗯,这里要注意:验证方不需要私钥,点乘结果正确性全靠公钥。硬件上我推荐用 Montgomery 点乘流水线,一次验证只要两次点乘。
4.2 SM2 公钥加密算法
加密算法有点像 ECIES 的变种,但用了 SM3 和 SM4(可选)做 KDF 和对称加密。流程是:
- 加密方: 选随机数 k,算点 C1 = k * G;再算点 S = h * PB(h 是 cofactor,SM2 曲线 h=1,其实 S=P_B),然后算点 k * PB = (x2, y2)
- 通过 KDF 从 x2||y2 导出密钥 t 比特,若 t 全零则重来
- 用 SM3 算 C3 = SM3(x2||M||y2)
- 对称加密(比如 SM4)得到 C2 = Enc(M)
- 输出密文 C1 || C3 || C2
解密方拿到 C1 后,先验证 C1 是否在曲线上(抵抗无效曲线攻击),然后用私钥 d 计算点 d * C1 = (x2, y2),再做 KDF 导出密钥,解密 C2,重新算哈希对比 C3。整个过程硬件上最耗时的是点验证和点乘,建议用专用点验证状态机,不要用通用点乘单元串行做。
4.3 密钥交换协议
SM2 密钥交换有点像 ECDH 的加强版,加入了临时密钥和身份认证。双方各有一个长期密钥对 (dA, PA) 和 (dB, PB),另选临时密钥对 (rA, RA) 和 (rB, RB)。
基本流程:
- A 发 RA 给 B,B 发 RB 给 A
- 双方各自计算:tA = (dA + rA * x1) mod n,tB = (dB + rB * x2) mod n(x1、x2 是对方临时点的 x 坐标)
- 然后算共享点 U = h * tA * (PB + x2 * RB)。注意 h=1 时可以简化
- 从 U 的 x2||y2 导出会话密钥
- 双方各自计算认证标签(用 SM3 算杂凑值)并交换验证
4.4 SM2 三种功能的硬件实现对比
| 功能 | 主要点乘次数 | 哈希调用 | 特殊风险 |
|---|---|---|---|
| 签名生成 | 1 次(固定基点 G) | 2 次 SM3 | 随机数 k 泄漏 → 私钥泄露 |
| 签名验证 | 2 次(G 和 PA) | 2 次 SM3 | 无效曲线攻击(需验证 PA 在曲线上) |
| 加密 | 2 次(G 和 PB) | KDF + 2 次 SM3 | C1 不在曲线上、KDF 输出长度错误 |
| 解密 | 1 次(C1) | KDF + 2 次 SM3 | 验证 C1 在曲线上 |
| 密钥交换 | 3 次(含临时点) | 3 次 SM3 | 参数顺序错乱、共享密钥不一致 |
4.5 核心流程图:SM2 签名与验证的过程
下面这张图概括了签名生成与验证的数据流,重点标出了 Z 值的计算入口和最终比较环节。硬件实现时,这两条路径可以共享点乘单元,但数据流控制逻辑要分开。
你看,左侧签名和右侧验证的很多计算步骤是镜像关系(Z、e 完全一样)。硬件里可以用同一组哈希引擎并行计算签名和验证,节省面积。不过要注意,签名时需要生成随机数 k,而验证不需要,所以随机数生成器只在签名路径上激活。我之前的做法是让点乘模块支持“固定基点乘”和“随机点乘”两种模式,通过微码控制寄存器切换。
4.6 避坑指南
- Z 值同步: 签名方和验证方的用户 ID 长度、字节序必须完全一致。我曾经吃过亏——两边 ID 传错了尾字节,导致 Z 值不同,签名死活验不过。
- 随机数复用: HSM 硬件里不要用同一个 TRNG 输出给多个并发签名线程,否则可能碰撞。我建议每个签名通道独立 TRNG 实例。
- 点乘旁路攻击: 固定基点乘可以用预计算表加速,但要注意抗侧信道;随机点乘用 Montgomery ladder 是最安全的。
- 无效曲线攻击: 收到对方公钥或临时点后,必须验证其是否在指定曲线上,且不是无穷远点。这个检查在硬件里可以用一个简单的自定义指令完成。