SM2签名验签机制:数字签名生成流程、验签流程、SM2签名数据结构(DER编码)
签名验签,说白了就是给固件贴上一张「防伪标签」。你想想看,固件在产线烧录、空中升级的时候,怎么保证它没被人动过手脚?SM2签名就是干这个的。
我刚开始接触SM2时,总觉得签名就是「私钥加密、公钥解密」那么简单。后来踩了坑才发现——SM2的签名机制跟RSA完全不是一回事。它的核心是椭圆曲线上的零知识证明,而不是加密的逆过程。
数字签名生成流程
SM2签名的生成,我习惯把它拆成三步:预处理、核心计算、DER编码。每一步都有坑,咱们一个一个说。
第一步:预处理
签名不是直接对原始固件哈希动手。SM2标准要求先做一次「用户身份+曲线参数+公钥」的杂凑,生成一个叫 ZA 的值。然后把这个 ZA 拼到消息哈希前面,再做一次SM3。
为什么要多此一举?
我在项目中遇到过有人跳过这一步,直接把固件哈希拿去签名。结果呢?攻击者可以伪造一个不同身份但相同哈希的消息,签名照样能通过。嗯,这里要注意——ZA 相当于把签名者的身份「钉死」在签名里,谁也赖不掉。
预处理公式:
M' = ZA || M(M为待签名消息)
e = SM3(M')(e为签名输入)
第二步:核心签名计算
这一步是椭圆曲线运算的重头戏。我直接给代码,你跟着走一遍就明白了。
// SM2签名核心算法(伪代码)
输入:私钥 dA,消息摘要 e
输出:签名值 (r, s)
1. 生成随机数 k ∈ [1, n-1] // n为椭圆曲线阶
2. 计算椭圆曲线点 (x1, y1) = [k]G // G为基点
3. 计算 r = (e + x1) mod n
4. 如果 r == 0 或 r + k == n,回到步骤1
5. 计算 s = ((1 + dA)^(-1) * (k - r * dA)) mod n
6. 如果 s == 0,回到步骤1
7. 输出 (r, s)
这里有个关键点——随机数 k 绝对不能重复使用。我曾经在调试一个IoT设备时,发现签名每次都一样。查了半天,原来是随机数生成器被初始化成固定种子了。攻击者拿到两个相同 k 的签名,分分钟就能把私钥算出来。这个坑,我劝你别踩。
⚠️ 随机数 k 的生死线:
- 每次签名必须生成新的 k
- k 必须真随机,不能伪随机
- k 不能泄露,哪怕泄露一次,私钥就没了
- k 不能为0,不能为 n-1
验签流程
验签是签名的逆过程,但注意——它不是解密。验签的本质是验证「签名者确实拥有私钥,且消息没有被篡改」。
我习惯把验签流程叫做「三查一算」:查范围、查曲线、查身份、算结果。
验签步骤
- 查范围:检查 r 和 s 是否在 [1, n-1] 区间内。不在?直接拒掉。
- 查曲线:计算 t = (r + s) mod n。t == 0?拒掉。
- 查身份:用签名者的公钥和身份信息重新计算 ZA,再算 e = SM3(ZA || M)。
- 算结果:计算椭圆曲线点 (x1', y1') = [s]G + [t]PA(PA为公钥)。如果 x1' == r,验签通过。
💡 我的验签小技巧:
实际项目中,我建议在验签前先做一次「格式校验」。很多攻击者会故意传畸形签名,试图触发缓冲区溢出。先检查 DER 编码长度是否在合理范围内(SM2签名固定为 70-72 字节),能挡掉一大半低级攻击。
SM2签名数据结构(DER编码)
签名值 (r, s) 不能直接扔给固件。我们需要一种标准化的打包方式——DER编码。DER是ASN.1的一种编码规则,说白了就是「TLV」:Tag + Length + Value。
SM2签名的DER结构长这样:
SEQUENCE {
INTEGER r, // 签名值r,32字节大端整数
INTEGER s // 签名值s,32字节大端整数
}
我画了一张图,帮你把DER编码的每一层拆开看:
DER编码的坑
我整理了一个表格,把DER编码中常见的坑和解决方案列出来:
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 前导零问题 | r或s的最高字节≥0x80时,DER要求前面补0x00 | 编码时检查最高位,必要时插入0x00 |
| 长度歧义 | 32字节整数可能编码为33字节(含前导零) | 固定r和s为32字节,前导零只补在最高位 |
| 签名长度不固定 | DER编码长度在70~72字节之间波动 | 解析时不要硬编码长度,按TLV结构解析 |
| 大端小端混淆 | ARM Cortex-M默认小端,SM2要求大端 | 编码前做字节序转换 |
⚠️ 我曾经踩过的坑:
有一次在STM32上做SM2验签,签名总是失败。查了两天,发现是DER解析器把r和s的长度字段当成了固定值。实际上,当r的最高字节小于0x80时,长度是32;大于等于0x80时,长度是33。这个「1字节」的差异,让我差点把开发板砸了。
实战建议
说了这么多,我总结几条实战中的铁律:
- 签名端:随机数k必须用硬件TRNG生成,别用软件伪随机。我在产线上见过用rand()签名的,结果两个设备签名一模一样——私钥当场暴露。
- 验签端:先做DER格式校验,再做密码学验签。很多攻击者会利用畸形DER编码绕过验签逻辑。
- 存储端:签名和固件要分开存储。我见过有人把签名嵌在固件头部,结果升级时签名跟着固件一起被篡改——成了「自己验自己」的笑话。
- 调试端:开发阶段建议打印r和s的十六进制值,方便对照标准向量。SM2的测试向量可以从GMT 0003.5-2012标准里找。
SM2签名验签,说白了就是「私钥签名、公钥验签、DER打包」。但每个环节都有细节,稍不注意就会出问题。我建议你在实现时,先把DER编码和解码的单元测试写到位,再去做签名验签的逻辑。这样出了问题,能快速定位是编码问题还是密码学问题。