安全启动链:从第一行代码开始的可信根基
安全启动链,说白了就是设备上电后,从硬件到操作系统的一连串「验身」过程。我做了这么多年嵌入式安全,见过太多设备因为启动链上的一个漏洞,整个系统就被攻破了。今天咱们就聊聊这个链条的起点——BootROM 设计、签名验证、回滚保护,还有那个最底层的硬件信任根。
BootROM:芯片上电后的第一口气
芯片一上电,CPU 从复位向量指向的位置开始执行。这个位置通常是一段固化在芯片内部的只读存储器,也就是 BootROM。它不可更改,所以也叫「硬编码引导程序」。
BootROM 的任务很简单:初始化最基本的硬件(时钟、内存控制器、安全引擎),然后去外部存储(比如 SPI Flash、eMMC)里找下一级引导程序。但这里有个关键——它必须验证下一级程序的合法性。
核心原则:BootROM 是信任链的根,它本身必须是不可篡改的。一旦 BootROM 被攻破,整个系统就完了。
我在一个 IoT 项目里遇到过,某厂商把 BootROM 放在了一块可重编程的 Flash 里。结果呢?攻击者通过电压毛刺攻击,直接改写了 BootROM 内容,绕过了所有后续验证。嗯,这设计从一开始就输了。
签名验证机制:谁来证明你是你
BootROM 验证下一级程序,靠的是数字签名。流程大致是这样的:
- BootROM 从外部存储读取镜像文件和它的签名
- 用硬件信任根里的公钥解密签名,得到哈希值
- 对镜像文件本身计算哈希,比对两者是否一致
这里用的算法,我建议至少是 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)是整个安全启动链的基石。它必须满足三个条件:
- 不可篡改:物理上无法修改
- 不可绕过:系统启动必须经过它
- 不可克隆:每个芯片的信任根是唯一的
最常见的硬件信任根实现方式有:
| 方案 | 存储介质 | 优点 | 缺点 |
|---|---|---|---|
| eFuse | 芯片内部熔丝 | 一次性编程,不可恢复 | 容量小,烧录后不可改 |
| OTP | 一次性可编程存储器 | 比 eFuse 容量大 | 需要额外工艺 |
| TPM | 独立安全芯片 | 功能完整,标准接口 | 成本高,面积大 |
| PUF | 物理不可克隆函数 | 无需存储密钥 | 稳定性受环境影响 |
我建议大多数嵌入式设备用 eFuse 就够了。把公钥哈希和版本号熔丝烧进去,成本低,安全性也够。只有对安全性要求极高的场景(比如金融支付终端),才需要上独立 TPM。
安全启动链的完整流程
下面这张图展示了从 BootROM 到操作系统加载的完整信任链:
你看,整个链条是一环扣一环的。BootROM 验证 BL1,BL1 验证 BL2,BL2 验证操作系统。每一级都用自己的公钥去验证下一级的签名。只要有一级被攻破,信任链就断了。
避坑指南:我踩过的那些坑
做了这么多年,我总结了几条血泪教训:
- 别把公钥放在 Flash 里:我曾经见过一个产品,公钥存在 SPI Flash 里。攻击者直接换掉 Flash 芯片,用自己的公钥签名恶意镜像,系统就沦陷了。公钥必须放在 eFuse 或 OTP 里。
- 验证失败要「硬」处理:别只是打印个错误日志就完事。我见过一个案例,验证失败后系统进入了一个「恢复模式」,结果攻击者利用这个模式刷入了恶意固件。正确的做法是:验证失败直接死循环或触发硬件复位。
- 回滚保护要跟版本号绑定:别只检查签名,不检查版本。攻击者可以拿一个旧版本的合法镜像来降级攻击。版本号必须存储在不可篡改的硬件里。
- 调试接口要封死:量产前一定要把 JTAG/SWD 接口熔断。否则攻击者可以直接通过调试接口读取信任根内容。
一个小技巧:在 BootROM 里加一个「安全启动日志」功能,把每次验证的结果(成功/失败、版本号、签名算法)记录到 OTP 里。这样即使设备被攻破了,你也能通过分析日志找到攻击路径。
安全启动链是设备安全的第一道防线。它不复杂,但细节决定成败。从 BootROM 的设计,到签名验证的实现,再到回滚保护和信任根的存储,每一步都不能马虎。我见过太多「差不多就行」的设计,最后都付出了惨痛的代价。
记住一句话:信任链的长度,决定了系统的安全强度。链条越短,越安全。但现实中我们往往需要多级启动,那就确保每一级都足够坚固。