4、镜像签名与验证:使用RSA/ECDSA对固件镜像签名,公钥嵌入BootROM的验证流程

好,咱们接着聊。上一节我们把安全启动的整个链路讲清楚了,这一节就深入最核心的一环——镜像签名与验证

说白了,就是给固件镜像加一把「锁」,然后在BootROM里用「钥匙」去开。这把锁和钥匙,就是非对称加密算法——RSA或者ECDSA。

4.1 为什么非对称?对称不行吗?

你可能会问:用AES加密镜像,然后BootROM里存个密钥,不也能防篡改吗?

嗯,这里有个关键区别。AES是对称加密,加密和解密用同一个密钥。这个密钥一旦被提取出来,攻击者就能自己加密一个恶意镜像,然后替换掉你的固件。

非对称加密,用的是「私钥签名,公钥验签」。私钥只有你(开发者/厂商)持有,公钥可以公开,甚至直接焊死在BootROM里。攻击者拿不到私钥,就永远无法伪造一个合法的签名。

我在项目中遇到过一件事:有个客户坚持用AES做固件加密,结果产品上市三个月,就被破解了。后来我帮他们改成ECDSA签名,至今没出过问题。说白了,签名是防篡改,加密是防窃取,安全启动的核心是前者。

4.2 签名流程:从固件到签名文件

先看签名端。你在PC上编译好固件,得到一个firmware.bin。接下来要做这几步:

  1. 计算哈希:对固件做SHA-256(或SHA-384/512),得到一个摘要。
  2. 私钥签名:用RSA或ECDSA私钥,对这个摘要进行签名,生成签名值。
  3. 打包:把固件、签名值、算法标识、版本号等信息,拼成一个完整的镜像包。

我习惯用OpenSSL来做签名。举个例子,用ECDSA P-256签名:

# 生成私钥
openssl ecparam -genkey -name prime256v1 -out private.pem

# 提取公钥
openssl ec -in private.pem -pubout -out public.pem

# 计算固件哈希并签名
openssl dgst -sha256 -sign private.pem -out firmware.sig firmware.bin

# 打包(简单示意)
cat firmware.bin firmware.sig > firmware_signed.bin

注意,实际产品中签名包的结构会更复杂。一般会包含:

  • 镜像头(magic number、版本、长度)
  • 固件数据
  • 签名值
  • 可能还有证书链

这里有个坑:签名时一定要包含版本号。我曾经见过一个方案,只对固件数据签名,没包含版本号。结果攻击者把旧版本固件(有已知漏洞)替换进来,签名还能通过。这就是典型的「降级攻击」。

避坑指南:签名时务必把版本号、设备ID、时间戳等元数据一起签名。否则攻击者可以玩「版本回滚」的把戏。

4.3 验证流程:BootROM里的「守门员」

现在镜像到了设备端。上电后,BootROM(只读存储器里的启动代码)开始执行验证。流程是这样的:

  1. 读取公钥:从BootROM的固定位置,取出预置的公钥。这个公钥是在芯片出厂时烧录的,不可更改。
  2. 解析镜像头:读取镜像的版本、长度、算法标识等信息。
  3. 计算哈希:对固件数据部分,用同样的哈希算法(SHA-256)计算摘要。
  4. 验签:用公钥、签名值、计算出的摘要,调用验签函数。如果通过,说明镜像未被篡改,且确实是私钥持有者签发的。
  5. 跳转执行:验签通过后,BootROM把控制权交给固件。

这个流程,说白了就是一句话:先验证,后执行。任何一步失败,直接死循环或进入恢复模式。

我建议你在BootROM里加一个「看门狗」:如果验签失败,不要立即复位,而是尝试从备份区加载固件。我在一个消防报警项目里就这么做的,有一次OTA升级中途断电,主镜像损坏,备份镜像自动顶上,设备没宕机。

4.4 RSA vs ECDSA:怎么选?

这是很多工程师纠结的问题。我直接给结论:

对比项 RSA ECDSA
密钥长度 2048/4096位 256/384位(等效安全强度)
签名速度 慢(私钥操作)
验签速度 较快
公钥大小 256/512字节 64/96字节
硬件支持 广泛 较新芯片支持
抗量子攻击 弱(但比RSA稍好)

我个人习惯:新项目优先选ECDSA P-256。原因很简单:公钥小,BootROM里省空间;验签快,启动时间短。而且现在主流MCU(比如STM32H7、NXP i.MX RT系列)都内置了硬件加速。

但如果你用的是老芯片,或者需要兼容某些标准(比如PKCS#1 v1.5),那RSA 2048也是稳妥的选择。

小技巧:ECDSA的随机数生成很重要。如果随机数质量差,私钥可能被推导出来。建议使用硬件TRNG(真随机数发生器),别用软件伪随机。

4.5 公钥嵌入BootROM的几种方式

公钥怎么放进去?有三种常见做法:

  • 硬编码:在BootROM源码里直接写一个数组。优点是简单,缺点是改不了。适合量产前就确定密钥的产品。
  • OTP(一次性可编程)存储:芯片有专门的efuse区域,出厂时烧录公钥哈希或公钥本身。优点是灵活,可以批量烧录不同密钥。我最近一个项目就用这种方式。
  • 证书链:BootROM只存根公钥,固件携带由根签名的子证书。优点是支持密钥轮换,缺点是验证逻辑复杂。

嗯,这里要注意:公钥本身也要防篡改。如果公钥存在Flash里,攻击者可以改掉它,然后用自己生成的私钥签名恶意固件。所以公钥要么放ROM,要么放OTP,要么用硬件保护(比如MPU禁止写)。

4.6 实战中的几个坑

最后,分享几个我踩过的坑:

  • 哈希碰撞:理论上SHA-256很安全,但如果你用MD5或SHA-1,那就等着被破解吧。别省那点计算时间。
  • 签名长度检查:验签时一定要检查签名值的长度是否合法。我曾经见过一个漏洞,攻击者传一个空签名,某些库会直接返回成功。
  • 时序攻击:验签函数如果用了不安全的比较(比如memcmp),攻击者可以通过测量响应时间来逐位猜测签名。一定要用常量时间比较函数。
  • 备份镜像:主镜像验签失败时,不要直接死机。尝试从备份区加载,并记录错误日志。这在消防系统里尤其重要——设备不能因为一次OTA失败就变砖。

好了,镜像签名与验证的核心内容就这些。下一节我们讲如何把公钥安全地部署到产线,以及如何管理密钥生命周期。到时候我会分享一个我亲手设计的密钥管理方案,保证实用。