安全启动链:从第一行代码开始的可信根基

安全启动链,说白了就是设备上电后,从硬件到操作系统的一连串「验身」过程。我做了这么多年嵌入式安全,见过太多设备因为启动链上的一个漏洞,整个系统就被攻破了。今天咱们就聊聊这个链条的起点——BootROM 设计、签名验证、回滚保护,还有那个最底层的硬件信任根。

BootROM:芯片上电后的第一口气

芯片一上电,CPU 从复位向量指向的位置开始执行。这个位置通常是一段固化在芯片内部的只读存储器,也就是 BootROM。它不可更改,所以也叫「硬编码引导程序」。

BootROM 的任务很简单:初始化最基本的硬件(时钟、内存控制器、安全引擎),然后去外部存储(比如 SPI Flash、eMMC)里找下一级引导程序。但这里有个关键——它必须验证下一级程序的合法性。

核心原则:BootROM 是信任链的根,它本身必须是不可篡改的。一旦 BootROM 被攻破,整个系统就完了。

我在一个 IoT 项目里遇到过,某厂商把 BootROM 放在了一块可重编程的 Flash 里。结果呢?攻击者通过电压毛刺攻击,直接改写了 BootROM 内容,绕过了所有后续验证。嗯,这设计从一开始就输了。

签名验证机制:谁来证明你是你

BootROM 验证下一级程序,靠的是数字签名。流程大致是这样的:

  1. BootROM 从外部存储读取镜像文件和它的签名
  2. 用硬件信任根里的公钥解密签名,得到哈希值
  3. 对镜像文件本身计算哈希,比对两者是否一致

这里用的算法,我建议至少是 RSA-2048 或 ECDSA-P256。SHA-1 就别想了,早就不安全了。

我的习惯:签名验证一定要在 BootROM 里用硬件加速引擎做,别用软件实现。软件跑 RSA 验证,慢不说,还容易引入侧信道漏洞。

你想想看,如果签名验证是软件实现的,攻击者完全可以在验证过程中注入故障,让「验证失败」变成「验证通过」。硬件引擎就不一样了,它是个黑盒,结果直接反映在状态寄存器里,软件改不了。

签名验证的典型流程

步骤 操作 谁执行
1 读取镜像头部,获取签名算法和密钥索引 BootROM
2 从 OTP/eFuse 读取公钥 硬件安全模块
3 用公钥解密签名,得到期望哈希 硬件加速器
4 对镜像内容计算实际哈希 硬件加速器
5 比较两个哈希值 比较器(硬件)
6 结果写入状态寄存器 硬件

我曾经在一个项目里发现,某芯片的 BootROM 在签名验证失败后,只是设置了一个标志位,然后继续执行。这太可怕了——攻击者只要让验证流程「看起来」走完了,就能绕过检查。正确的做法是:验证失败直接死循环或触发硬件复位,不给任何执行恶意代码的机会。

回滚保护:别让旧版本的漏洞回来找你

签名验证只能保证镜像来自合法厂商,但保证不了它是最新版本。攻击者可以保存一个旧版本的合法镜像,等厂商修复了漏洞后,再把这个旧版本刷回去。这就是回滚攻击。

回滚保护的思路很简单:让设备记住「当前允许运行的最低版本号」。每次升级时,新版本的版本号必须大于这个最低值。

注意:版本号存储的位置必须是不可篡改的。如果存在 Flash 里,攻击者可以直接改掉。必须用 eFuse 或 OTP,一次写入,永不回退。

常见的回滚保护方案

  • 单调计数器:每次升级递增一个硬件计数器,旧版本无法匹配
  • 版本号熔丝:在 eFuse 里烧录最低允许版本,升级时只能烧更高版本
  • 安全存储区:将版本号放在受 TEE 保护的存储区域

我个人比较推荐单调计数器。它简单、可靠,而且硬件实现成本低。我在一个车规级项目里用过,计数器从 0 开始,每次安全升级加 1。攻击者就算拿到了旧镜像,计数器值对不上,BootROM 直接拒绝启动。

硬件信任根:一切信任的起点

信任根(Root of Trust, RoT)是整个安全启动链的基石。它必须满足三个条件:

  1. 不可篡改:物理上无法修改
  2. 不可绕过:系统启动必须经过它
  3. 不可克隆:每个芯片的信任根是唯一的

最常见的硬件信任根实现方式有:

方案 存储介质 优点 缺点
eFuse 芯片内部熔丝 一次性编程,不可恢复 容量小,烧录后不可改
OTP 一次性可编程存储器 比 eFuse 容量大 需要额外工艺
TPM 独立安全芯片 功能完整,标准接口 成本高,面积大
PUF 物理不可克隆函数 无需存储密钥 稳定性受环境影响

我建议大多数嵌入式设备用 eFuse 就够了。把公钥哈希和版本号熔丝烧进去,成本低,安全性也够。只有对安全性要求极高的场景(比如金融支付终端),才需要上独立 TPM。

安全启动链的完整流程

下面这张图展示了从 BootROM 到操作系统加载的完整信任链:

安全启动链完整流程 BootROM 硬件信任根 (eFuse/OTP) 签名验证 回滚保护检查 加载 BL1 (SPL) BL1 验证 BL2 加载 BL2 (U-Boot) 验证并加载 OS 信任链传递 每一级验证下一级 直到操作系统加载完成 失败处理 任何一级验证失败 立即停止启动

你看,整个链条是一环扣一环的。BootROM 验证 BL1,BL1 验证 BL2,BL2 验证操作系统。每一级都用自己的公钥去验证下一级的签名。只要有一级被攻破,信任链就断了。

避坑指南:我踩过的那些坑

做了这么多年,我总结了几条血泪教训:

  • 别把公钥放在 Flash 里:我曾经见过一个产品,公钥存在 SPI Flash 里。攻击者直接换掉 Flash 芯片,用自己的公钥签名恶意镜像,系统就沦陷了。公钥必须放在 eFuse 或 OTP 里。
  • 验证失败要「硬」处理:别只是打印个错误日志就完事。我见过一个案例,验证失败后系统进入了一个「恢复模式」,结果攻击者利用这个模式刷入了恶意固件。正确的做法是:验证失败直接死循环或触发硬件复位。
  • 回滚保护要跟版本号绑定:别只检查签名,不检查版本。攻击者可以拿一个旧版本的合法镜像来降级攻击。版本号必须存储在不可篡改的硬件里。
  • 调试接口要封死:量产前一定要把 JTAG/SWD 接口熔断。否则攻击者可以直接通过调试接口读取信任根内容。

一个小技巧:在 BootROM 里加一个「安全启动日志」功能,把每次验证的结果(成功/失败、版本号、签名算法)记录到 OTP 里。这样即使设备被攻破了,你也能通过分析日志找到攻击路径。

安全启动链是设备安全的第一道防线。它不复杂,但细节决定成败。从 BootROM 的设计,到签名验证的实现,再到回滚保护和信任根的存储,每一步都不能马虎。我见过太多「差不多就行」的设计,最后都付出了惨痛的代价。

记住一句话:信任链的长度,决定了系统的安全强度。链条越短,越安全。但现实中我们往往需要多级启动,那就确保每一级都足够坚固。