1. 安全启动概述

各位同学好,我是老周。今天咱们开始聊安全启动。说实话,我做了十几年嵌入式固件,见过太多因为启动环节出问题导致整个产品报废的案例。安全启动这个话题,说白了就是解决一个核心问题:你怎么确定你的设备启动时,跑的是你写的代码?

什么是安全启动

安全启动(Secure Boot)不是某个芯片厂商的专利技术,而是一套从硬件复位开始,逐级校验软件完整性和合法性的机制。它的核心思想很简单:只运行经过签名的、未被篡改的代码。

我习惯把安全启动比作机场安检。你想想看,旅客(代码)要登机(执行),必须经过身份验证和行李检查(签名校验)。如果发现可疑物品(篡改),直接拦下。没有登机牌?对不起,请回。

安全启动的核心目标:

  • 完整性:确保代码在存储或传输过程中没有被修改
  • 合法性:确保代码来自可信的发布者(通常是设备制造商)
  • 不可抵赖性:发布者不能否认自己签发了某份固件

这里有个容易混淆的点:安全启动不等于加密启动。加密启动是为了防止别人读取你的固件内容,而安全启动是为了防止别人运行篡改过的固件。我在项目里见过有人把这两个概念搞混,结果只做了加密没做签名校验,被攻击者用重放攻击直接打穿。

为什么需要安全启动

这个问题我问过不少刚入行的工程师。有人回答「客户要求的」,有人回答「为了过认证」。这些都对,但没说到根子上。

咱们直接看几个真实场景:

攻击场景 没有安全启动的后果 有安全启动的保护
攻击者替换Bootloader 设备启动恶意代码,完全失控 签名校验失败,拒绝启动
固件降级攻击 回滚到有漏洞的旧版本 版本号+签名双重校验,阻止降级
物理SPI Flash篡改 直接读取Flash,修改后写回 修改后的固件签名无效,无法通过校验
OTA中间人攻击 下载被植入后门的固件 签名不匹配,更新包被拒绝

我曾经接手过一个IoT网关的项目,出厂时没做安全启动。结果客户反馈设备经常「变砖」,远程排查发现是攻击者通过调试接口注入了恶意Bootloader。那次教训很深刻——补安全启动方案时,硬件已经定型,只能靠软件打补丁,效果大打折扣。

注意:安全启动不是万能的。它解决的是启动阶段的信任问题,但无法防御运行时的漏洞利用。比如你的应用层有缓冲区溢出,攻击者照样可以通过ROP链执行任意代码。安全启动只是安全体系的第一道门,不是最后一道。

安全启动的信任链模型

这是整个安全启动的核心。我画了一张图,咱们边看边聊。

安全启动信任链模型 安全启动信任链模型 硬件根信任 ROM Code / eFuse BootROM → Bootloader 校验Bootloader签名和完整性 Bootloader → OS Kernel 校验内核镜像签名和完整性 OS Kernel → 应用/服务 校验应用模块签名和完整性 关键点: 每一级只信任上一级传递过来的公钥和签名,形成「链式信任」。任何一环断裂,启动过程立即终止。 信任等级 不可篡改 硬件保护 软件校验 逐级传递 信任传递方向

这张图展示的就是典型的信任链模型。我解释一下每一层的作用:

第一层:硬件根信任

这是整个信任链的基石。通常固化在芯片的ROM里,或者通过eFuse烧录。ROM Code是芯片出厂时写死的,物理上无法修改。它里面存着芯片厂商的公钥,用来验证下一级Bootloader的签名。

我记得有一次调试一个ARM Cortex-M的芯片,发现ROM Code里竟然有个bug,导致在某些条件下会跳过签名校验。那真是让人头大的问题——因为ROM Code没法打补丁,只能通过后续的Bootloader做二次校验来弥补。

个人经验:硬件根信任的选择非常关键。我建议优先使用芯片内置的ROM Code作为信任根,而不是依赖外部Flash。外部Flash可以被替换,但ROM Code是物理不可更改的。如果芯片支持eFuse烧录,记得在生产时把调试接口永久关闭,否则攻击者可以通过JTAG/SWD绕过信任链。

第二层:BootROM验证Bootloader

芯片上电后,CPU首先执行ROM Code。ROM Code从预定义的存储位置(比如SPI Flash的起始地址)读取Bootloader,然后用内置的公钥验证Bootloader的数字签名。验证通过,才把控制权交给Bootloader。

这里有个细节:Bootloader本身通常分为两个阶段——SPL(Secondary Program Loader)和主Bootloader。SPL非常小,主要负责初始化DDR和存储控制器,然后加载主Bootloader。我习惯把SPL也纳入信任链,让ROM Code直接验证SPL,SPL再验证主Bootloader。

第三层:Bootloader验证OS Kernel

Bootloader拿到控制权后,会加载操作系统内核。在加载之前,它需要用自己信任的公钥验证内核镜像的签名。这个公钥可以存储在Bootloader的代码段里,或者从eFuse中读取。

你可能会问:Bootloader的公钥从哪来?答案是:由上一级(ROM Code)通过签名链传递下来。ROM Code验证Bootloader时,同时也验证了Bootloader携带的公钥是否合法。这就是「链式信任」的含义。

第四层:OS验证应用和服务

内核启动后,会加载各种应用模块、驱动、服务。这些模块同样需要签名校验。Linux内核的IMA(Integrity Measurement Architecture)和dm-verity就是干这个的。在RTOS里,通常由安全启动框架统一管理。

信任链的完整性要求:

  • 每一级必须严格验证下一级的签名
  • 签名密钥必须硬件保护,不能暴露给软件
  • 校验失败必须立即停止启动,不能回退到不安全模式
  • 信任链不能有「跳过」或「可选」的环节

说到这,我想起一个真实案例。某厂商的智能门锁,Bootloader验证内核时,如果签名校验失败,会尝试加载一个「恢复模式」的内核。这个恢复模式内核没有签名校验,结果攻击者直接利用这个后门刷入了恶意固件。嗯,这就是典型的信任链断裂——校验失败应该直接停机,而不是提供降级路径。

信任链的两种实现方式

在实际项目中,我见过两种主流的信任链实现方式:

特性 静态信任链 动态信任链
校验时机 启动时一次性校验 启动时校验 + 运行时持续校验
密钥管理 公钥固化在ROM/Bootloader 公钥可更新(需签名授权)
典型应用 IoT设备、工控设备 手机、平板、PC
安全性 中等(启动后不再校验) 高(运行时也防篡改)
实现复杂度 低 高

静态信任链适合资源受限的嵌入式设备。我做过一个基于STM32的项目,Flash只有512KB,根本跑不动复杂的运行时校验。那就老老实实做好启动时的签名校验,配合硬件防读保护,也能达到不错的安全等级。

动态信任链则适合对安全要求极高的场景。比如手机里的Android Verified Boot 2.0,不仅启动时校验,运行时还有dm-verity持续检查文件系统的完整性。代价是性能开销和存储空间占用都更大。

避坑指南:我曾经在一个项目里选了动态信任链,但没考虑好密钥轮换策略。结果产品上市两年后,发现签名私钥有泄露风险,但公钥固化在ROM里没法更新。最后只能召回设备换芯片,损失惨重。所以,密钥管理方案一定要在设计阶段就规划好,包括密钥生成、存储、轮换和撤销机制。

好了,安全启动的概述就聊到这。信任链模型是整个安全启动的骨架,后面的章节我们会逐一深入每个环节的具体实现。记住一句话:信任不能传递,必须验证。