3. SM2 数字签名原理:签名生成、验证与固件完整性校验

好,咱们进入第三章。数字签名这块,说白了就是给固件盖个「防伪章」。你想想看,物联网设备升级固件时,怎么知道下载下来的 bin 文件没被人动过手脚?SM2 数字签名就是干这个的。

我个人习惯把签名流程拆成三块来看:密钥对生成、签名生成、签名验证。咱们一个一个说。

3.1 密钥对生成:SM2 的「锁」与「钥匙」

SM2 基于椭圆曲线密码学,用的是国家密码管理局推荐的 sm2p256v1 曲线。密钥对生成其实不复杂,核心就两步:

  1. 随机选一个整数 d,范围在 [1, n-2] 之间,这就是私钥。
  2. 计算公钥 P = [d]G,G 是椭圆曲线的基点。

嗯,这里要注意:私钥必须用真随机数生成器。我在项目中遇到过有人用伪随机数生成私钥,结果两个设备生出了相同的密钥对——这问题在物联网海量部署时特别容易踩坑。

密钥对关键参数(SM2 推荐曲线)

参数说明长度
私钥 d随机整数,需保密256 位
公钥 P椭圆曲线上的点 (x, y)512 位(未压缩)
基点 G曲线上的固定生成元512 位
阶 nG 的阶,素数256 位

3.2 签名生成流程:给固件「盖戳」

签名生成,我习惯叫它「盖戳」过程。SM2 签名算法是国标 GM/T 0003.2-2012 规定的,流程如下:

  1. 预处理消息 M:计算 M 的哈希值 e = H(M),SM2 默认使用 SM3 哈希算法。
  2. 生成随机数 k:k ∈ [1, n-1],每次签名必须重新生成。
  3. 计算椭圆曲线点 (x1, y1) = [k]G。
  4. 计算 r = (e + x1) mod n。如果 r = 0 或 r + k = n,回到步骤 2。
  5. 计算 s = ((1 + d)^(-1) * (k - r * d)) mod n。如果 s = 0,回到步骤 2。
  6. 输出签名 (r, s),长度各 256 位,共 512 位。

避坑指南:我曾经在产线上发现签名偶尔失败,排查半天发现是随机数生成器被重复调用了。记住:每次签名必须用新的随机数 k,否则私钥会被推导出来。这不是理论问题,是真有人因此丢过私钥。

3.3 签名验证流程:验明正身

验证方拿到固件 M、签名 (r, s) 和公钥 P 后,按以下步骤验真伪:

  1. 检查 r, s 范围:r, s ∈ [1, n-1],不满足直接拒绝。
  2. 计算 e = H(M),同样用 SM3。
  3. 计算 t = (r + s) mod n,如果 t = 0,拒绝。
  4. 计算椭圆曲线点 (x1', y1') = [s]G + [t]P。
  5. 计算 R = (e + x1') mod n。
  6. 判断 R == r?相等则验证通过,否则拒绝。

你想想看,这个验证过程只用到了公钥,私钥全程没出现。这就是非对称签名的精髓——公钥可以公开,私钥必须锁死。

注意:验证时一定要做 r, s 范围检查。我见过有人偷懒跳过这步,结果被签名延展性攻击钻了空子。虽然 SM2 本身抗延展性比 ECDSA 强,但规范里的检查一个都不能省。

3.4 在固件完整性校验中的应用

好了,理论讲完,咱们看看实际怎么用。物联网固件保护,核心就两个场景:

3.4.1 固件发布签名(生产端)

设备厂商在编译完固件后,用 SM2 私钥对固件哈希签名。流程如下:

  • 计算固件文件的 SM3 哈希值。
  • 用 SM2 私钥对哈希值签名,生成 (r, s)。
  • 将签名附加到固件包中,或者单独存储。
// 伪代码示例:固件签名
firmware_hash = SM3(firmware_binary)
signature = SM2_sign(private_key, firmware_hash)
// 输出:firmware_binary + signature

3.4.2 固件升级验证(设备端)

设备收到固件包后,用预置的公钥验证签名:

  • 提取固件哈希和签名。
  • 用公钥验证签名是否匹配。
  • 匹配则升级,不匹配则拒绝。
// 伪代码示例:固件验证
received_hash = SM3(received_firmware)
is_valid = SM2_verify(public_key, received_hash, signature)
if is_valid:
    proceed_upgrade()
else:
    reject_firmware()

关键点:设备端的公钥必须安全存储。我建议放在芯片的 OTP(一次性可编程)区域或安全元件中,防止被替换。曾经有厂商把公钥明文写在 Flash 里,结果攻击者直接改了公钥,签了个恶意固件就通过了验证——这教训够深刻吧?

3.5 核心流程总览

下面这张图把签名生成和验证的完整流程串起来了,我建议你多看几遍,把每一步的输入输出搞清楚。

SM2 数字签名完整流程 签名生成(生产端) 固件二进制 M 输入:待签名的原始固件 SM3 哈希计算 输出:256 位哈希值 e SM2 签名运算 输入:私钥 d + 哈希 e + 随机数 k 输出签名 (r, s) r, s 各 256 位,共 512 位 打包固件 + 签名 发布到升级服务器 签名验证(设备端) 接收固件 + 签名 (r, s) 从升级包中提取 SM3 哈希计算 输出:256 位哈希值 e' SM2 验证运算 输入:公钥 P + 哈希 e' + 签名 (r, s) 验证结果 R == r ? 通过 : 拒绝 通过 → 执行升级 拒绝 → 丢弃固件 网络传输

3.6 实际部署中的几个要点

最后,我根据实际项目经验,给你列几个部署时容易忽略的点:

  • 签名性能:SM2 签名比 RSA 快不少,但在低端 MCU(比如 Cortex-M0)上,一次签名验证可能要几百毫秒。建议在设备启动时做一次验证就够了,不用每次读块都验。
  • 密钥生命周期:私钥不能永远不变。我建议每半年或每次固件大版本更新时轮换一次密钥对,旧公钥可以保留在设备上用于验证旧版本固件。
  • 防回滚攻击:签名只能保证固件没被篡改,但防不了攻击者拿旧版本固件来降级。解决办法是在签名时带上版本号或时间戳,设备端验证时检查版本是否高于当前版本。

个人经验:我曾经帮一家智能家居厂商做固件安全方案,他们一开始只做了签名没做版本检查。结果攻击者把旧版本固件(有已知漏洞)刷进去,绕过了所有安全机制。后来我们在签名数据里嵌入了固件版本号,设备端强制版本递增,才彻底堵住这个漏洞。

好了,SM2 数字签名的原理和应用就讲到这里。记住:签名是手段,不是目的。最终目标是在设备端建立起「信任链」——从 BootROM 到引导加载程序,再到固件,每一级都要验证下一级的签名。这个咱们后面章节会详细展开。