4、国密SM2签名算法:椭圆曲线基础与启动校验实战
SM2签名算法,说白了就是国密体系里的“数字手印”。你写了一段固件,怎么证明它是你写的、没人改过?SM2就是干这个的。我最早接触它是在一个电力终端的项目里,当时甲方要求启动时必须验签,否则不给过。嗯,从那以后我就把SM2的细节摸了个透。
4.1 SM2椭圆曲线密码学基础
椭圆曲线密码学(ECC)的核心,是在一个有限域上定义一条椭圆曲线,然后利用曲线上点的“加法”构成一个循环群。SM2使用的曲线参数是国家密码管理局定义的,我直接列出来:
| 参数 | 值(十六进制) | 说明 |
|---|---|---|
| p | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF | 素数模数,256位 |
| a | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC | 曲线系数 |
| b | 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93 | 曲线系数 |
| Gx | 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7 | 基点G的x坐标 |
| Gy | BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0 | 基点G的y坐标 |
| n | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123 | 基点G的阶 |
你想想看,为什么SM2要用256位的曲线?因为它的安全强度相当于RSA-3072,但密钥长度只有32字节。在嵌入式环境里,这太重要了——Flash和RAM都金贵,能省一点是一点。
4.2 SM2签名与验签流程
SM2的签名流程,我习惯把它拆成三步:预处理、签名生成、签名验证。咱们先看签名生成:
4.2.1 签名生成(Sign)
- 计算消息摘要:使用SM3对消息M计算杂凑值e = SM3(M)。
- 生成随机数k:在[1, n-1]范围内生成一个随机数。注意,这个k必须每次都不一样,而且绝对不能泄露。
- 计算椭圆曲线点:(x1, y1) = k * G,取x1。
- 计算r:r = (e + x1) mod n。如果r == 0或r + k == n,回到步骤2重新生成k。
- 计算s:s = ((1 + dA)^(-1) * (k - r * dA)) mod n。如果s == 0,回到步骤2。
- 输出签名:(r, s),每个都是32字节,共64字节。
这里有个坑,我必须要说。随机数k如果重复使用,私钥dA就能被算出来。我在一个IoT设备里见过,因为随机数生成器没种子,每次上电签名用的k都一样。攻击者拿到两个签名,直接就把私钥还原了。所以,随机数质量是SM2签名的命门。
4.2.2 签名验证(Verify)
- 检查r和s:必须在[1, n-1]范围内,否则直接拒绝。
- 计算消息摘要:e' = SM3(M)。
- 计算t:t = (r + s) mod n。如果t == 0,验签失败。
- 计算椭圆曲线点:(x1', y1') = s * G + t * PA,其中PA是公钥。
- 计算R:R = (e' + x1') mod n。
- 比较:如果R == r,验签通过;否则失败。
4.3 SM2在启动校验中的应用
启动校验,说白了就是让BootROM在加载固件之前,先验一下签名。流程大概是这样的:
这个流程看起来简单,但实际落地时有很多细节。我挑几个关键点说说:
4.3.1 公钥的存储与保护
公钥PA必须放在BootROM里,而且BootROM本身要写保护。我见过有些方案把公钥放在Flash的某个扇区,结果攻击者通过SPI接口把公钥替换成自己的,然后用自己的私钥签名恶意固件——整个验签就形同虚设了。
4.3.2 签名与固件的绑定
签名(r, s)通常放在固件头部,紧跟在固件镜像之前。我建议的固件布局是这样的:
| 偏移 | 内容 | 大小 | 说明 |
|---|---|---|---|
| 0x0000 | 签名r | 32字节 | SM2签名第一部分 |
| 0x0020 | 签名s | 32字节 | SM2签名第二部分 |
| 0x0040 | 固件长度 | 4字节 | 小端格式 |
| 0x0044 | 固件版本 | 4字节 | 用于版本回退检测 |
| 0x0048 | 固件数据 | 可变 | SM3摘要的输入 |
4.3.3 性能优化
SM2验签涉及两次点乘运算(s * G 和 t * PA),在低主频MCU上可能耗时几百毫秒。我的优化建议:
- 预计算:把基点G的倍数点提前算好存起来,减少实时计算量。
- 使用硬件加速器:很多国产MCU内置SM2/SM3硬件模块,验签时间能压到10ms以内。
- 并行化:如果BootROM支持双核,可以让一个核算SM3摘要,另一个核准备点乘数据。
4.4 常见问题与避坑
最后,我总结几个实际项目中容易踩的坑:
- 随机数重复:前面说过,k重复等于私钥泄露。解决方案是使用硬件TRNG,或者至少用一个带熵池的DRBG。
- 签名长度检查:验签时一定要检查r和s是否在[1, n-1]范围内。我曾经见过一个攻击,通过构造r=0的签名让验签逻辑进入死循环。
- 公钥点校验:验签前要确认公钥PA在曲线上,且不是无穷远点。否则攻击者可以用一个无效公钥绕过验签。
- 时序攻击:SM2的点乘运算如果时间不恒定,攻击者可以通过测量验签时间来推断私钥。建议使用蒙哥马利阶梯等恒定时间算法。
嗯,SM2签名这块内容不少,但核心就三点:曲线参数别搞错、随机数要真随机、公钥要锁死。把这三点守住,启动校验就稳了八成。