2. SM2 算法原理:椭圆曲线密码学、密钥对生成、签名验签机制
做国密适配,绕不开 SM2。说白了,它就是个基于椭圆曲线的公钥密码体系。跟 ECDSA 有点像,但细节上改了不少。我最早接触 SM2 时,第一反应是「这不就是 ECDSA 换了个曲线吗?」后来踩过坑才明白——参数不同、哈希绑定不同、签名流程也有讲究。
这一章,我带你把核心东西捋一遍。
2.1 椭圆曲线密码学基础
SM2 用的是一条定义在素数域 Fp 上的椭圆曲线。具体参数是国家规定的,你不需要自己算,直接用就行。
SM2 标准曲线参数(SM2 推荐参数)
- 素数 p:0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
- 系数 a:0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
- 系数 b:0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
- 基点 G:压缩形式 X 0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
- 阶 n:0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
- 余因子 h:1
这里要注意,基点 G 的阶 n 是一个大素数,保证了子群具备抗离散对数攻击的能力。你想想看,如果 n 有光滑因子,那私钥就容易算出来了。
椭圆曲线的加法、倍点、标量乘法是底层运算。我个人习惯把标量乘法理解为「私钥 d 与基点 G」做 d 次倍点再加起来的运算。嗯,实际用 double-and-add 算法加速。
2.2 密钥对生成
SM2 密钥对生成非常简单,就两步:
- 随机选择一个整数 d,范围 [1, n-2]。这就是私钥。
- 计算公钥 P = d * G(椭圆曲线标量乘法)。
返回的 P 是一个点,包含 (x, y) 坐标,通常用 64 字节非压缩格式或 33 字节压缩格式存储。我在项目中遇到过有人直接把公钥截成 32 字节,导致验签失败——这坑我帮人排查过,坐标必须完整。
注意:私钥 d 必须使用安全的随机数发生器生成。曾经有厂商因为用了伪随机种子导致私钥可预测,那安全性就全没了。
为了直观,我把核心流程画成一张图。
2.3 签名验签机制
SM2 的签名算法不是直接对消息签名,而是先算消息摘要(SM3)再加上用户标识 ZA,最后用椭圆曲线运算生成 (r, s)。为什么这么做?因为要把公钥和签名者身份绑进去,防止公钥替换攻击。
我个人的一个小技巧: 如果你在做网关接入,注意两端的用户标识 ZA 必须一致。我曾经排查过半天,最后发现是标识末尾的 '\0' 没对齐。
下面是签名和验签的核心步骤,我简化成表格方便对比:
| 步骤 | 签名 (Sign) | 验签 (Verify) |
|---|---|---|
| 1 | 计算 ZA = HSM3(ENTLA || IDA || a || b || xG || yG || xP || yP) | 同样计算 ZA(用传入的公钥) |
| 2 | M' = ZA || M,计算 e = HSM3(M') | M' = ZA || M,计算 e' = HSM3(M') |
| 3 | 生成随机数 k ∈ [1, n-1],计算 (x1, y1) = k·G | 计算 t = (r + s) mod n,若 t == 0 则验签失败 |
| 4 | 计算 r = (e + x1) mod n,若 r == 0 或 r + k == n 则回到步骤3 | 计算 (x2, y2) = s·G + t·P |
| 5 | 计算 s = ((1 + d)−1 * (k − r·d)) mod n,若 s == 0 则回到步骤3 | 计算 R = (e' + x2) mod n,若 R == r 则验签通过,否则失败 |
| 输出 | 签名 (r, s) | 通过/不通过 |
这里有个小坑:步骤3里随机数 k 必须每次重新生成,不能重复。我记得早些年的某个实现把 k 写死了,结果同一个私钥签出来的 r 全部一样,攻击者直接能反推出私钥。嗯,这类问题在商用密码检测中查得很严。
对比 ECDSA,SM2 的签名公式特意用了 s = (1+d)−1 * (k − r·d) 而不是 ECDSA 的 s = k−1 * (e + r·d)。这样做的好处是逆运算次数少了一个,算起来更快一点——当然主要目的是为了规避专利。
关键安全检查点(BC 检测或密评必查)
- 私钥 d 不能为 0 或 1,且必须来自合格随机数源
- 签名时 k 不可重用,不可预测
- 验签时先检查 r 和 s 是否在 [1, n-1] 范围内,避免畸形签名
- r + s 不能等于 n
最后说一句,SM2 的签名长度是固定 64 字节(两个 32 字节大整数 r 和 s),比 RSA 的签名短得多。在 IoT 设备上做适配,这个优势很明显。我做 SD-WAN 网关时,包里塞 SM2 签名完全没压力。
好了,这部分原理说完,你下一步看代码时思路会更清楚。后面章节会对接具体的 API 和改造示例。