2. Secure Boot 原理与实现:启动链信任根、Vector Bootloader 安全启动流程、签名与验签机制
各位同学,咱们今天聊聊 Secure Boot。说实话,这个主题在嵌入式安全里属于「地基」级别的存在。你想想看,如果芯片上电后第一段代码就被篡改了,那后面所有的安全措施都是白搭。我个人习惯把 Secure Boot 比作「看门老头」——他得确保进门的每一个人都是真货。
2.1 启动链信任根:一切安全的起点
什么叫信任根?说白了,就是芯片内部一个不可篡改的「初始信任点」。这个点通常固化在 ROM 里,或者通过硬件熔丝(eFuse)烧录进去。我在项目中遇到过最典型的场景:客户要求 OTA 升级,但死活不放心升级包的安全性。嗯,这时候信任根就派上用场了。
信任根的核心要素有三点:
- 不可篡改性:ROM 代码在芯片出厂后就无法修改,硬件熔丝一旦烧断就不可恢复。
- 最小化原则:信任根只做一件事——验证下一级 Bootloader 的签名。别让它干别的,干得越多漏洞越多。
- 硬件隔离:信任根运行在安全环境中,比如 ARM TrustZone 的安全世界,或者独立的 HS(Hardware Security)模块。
关键点:信任根不是一段代码,而是一个「信任链的锚点」。它本身不需要被验证,因为它就是验证的起点。
我曾经踩过一个坑:某款芯片的信任根放在 Flash 里,结果攻击者通过电压毛刺攻击跳过了信任根的校验。从那以后,我坚持信任根必须放在 ROM 或一次性可编程存储器(OTP)里。
2.2 Vector Bootloader 安全启动流程
Vector 的 Bootloader 安全启动流程,我把它总结为「三级火箭」模型。每一级只做一件事:验证下一级,然后交出控制权。
具体流程如下:
- 一级启动(ROM Boot):芯片上电后,CPU 从复位向量地址取指。这个地址指向 ROM 中的一段固化代码。ROM Boot 会检查复位原因、初始化硬件最小系统(时钟、SRAM),然后从预定义的存储位置(比如 Flash 的固定偏移地址)加载二级 Bootloader 的头部信息。
- 二级启动(Vector Bootloader):ROM Boot 验证二级 Bootloader 的签名。验证通过后,跳转到二级 Bootloader 的入口。Vector Bootloader 负责更复杂的初始化,比如 DDR、外设时钟、Flash 控制器。同时,它还会验证三级 Bootloader(也就是用户应用程序)的签名。
- 三级启动(用户应用程序):Vector Bootloader 验证用户应用程序的签名和完整性。验证通过后,跳转到应用程序入口。应用程序开始运行。
我的经验:在实际项目中,我建议在每一级启动之间加入「看门狗喂狗」操作。为什么?因为签名验证可能耗时较长,如果看门狗超时复位,系统就会陷入死循环。我曾经调试过一个 Bug,就是二级 Bootloader 验证大固件时看门狗没喂,结果系统反复重启。
这里有个表格,对比了三级启动的职责和特点:
| 启动级别 | 存储位置 | 主要职责 | 验证对象 |
|---|---|---|---|
| 一级(ROM Boot) | 芯片内部 ROM | 最小硬件初始化,加载二级 Bootloader | 二级 Bootloader 签名 |
| 二级(Vector Bootloader) | Flash 固定区域 | 完整硬件初始化,验证用户应用程序 | 用户应用程序签名 |
| 三级(用户应用程序) | Flash 用户区域 | 执行用户功能 | 无(已被验证) |
2.3 签名与验签机制
签名和验签,说白了就是「盖章」和「验章」。签名是私钥干的活,验签是公钥干的活。私钥必须保密,公钥可以公开。我在项目中见过最蠢的操作——把私钥硬编码在代码里。嗯,那跟没锁门有什么区别?
常见的签名算法有 RSA 和 ECDSA。我个人更倾向于 ECDSA,因为它在相同安全强度下密钥更短,计算速度更快。对于资源受限的嵌入式设备,这很重要。
签名流程大致如下:
- 对固件进行哈希计算,得到摘要(比如 SHA-256)。
- 用私钥对摘要进行加密,生成签名。
- 将签名附加在固件尾部,或者存储在固定偏移位置。
验签流程则相反:
- 读取固件,计算哈希摘要。
- 用公钥解密签名,得到原始摘要。
- 比较两个摘要。如果一致,说明固件未被篡改且来源可信。
注意:公钥的存储位置至关重要。如果公钥可以被攻击者篡改,那签名验证就形同虚设。我建议将公钥哈希烧录在 eFuse 中,验签时先验证公钥的完整性,再用公钥验签。这叫「公钥的信任锚」。
下面是一个简化的签名验签代码示例(伪代码):
// 签名过程(在安全主机上执行)
uint8_t firmware_hash[32];
SHA256_Calculate(firmware, firmware_size, firmware_hash);
uint8_t signature[256];
RSA_Sign(private_key, firmware_hash, signature);
// 将 signature 附加到 firmware 尾部
// 验签过程(在目标设备上执行)
uint8_t firmware_hash[32];
SHA256_Calculate(firmware, firmware_size, firmware_hash);
uint8_t decrypted_hash[32];
RSA_Verify(public_key, signature, decrypted_hash);
if (memcmp(firmware_hash, decrypted_hash, 32) == 0) {
// 验签通过,启动固件
} else {
// 验签失败,进入错误处理
}
你可能会问:「为什么不用对称加密?」对称加密确实快,但密钥分发是个大问题。你想想看,如果每个设备都用同一个密钥,那一个设备被破解,所有设备都完蛋。非对称加密的私钥只保存在服务器端,设备端只有公钥,安全性高得多。
最后,我想强调一点:Secure Boot 不是万能的。它只能保证启动过程的完整性,但无法防御运行时的攻击。比如,攻击者可以通过缓冲区溢出注入代码,绕过 Secure Boot 的防护。所以,Secure Boot 必须和其他安全机制(比如 MPU、TrustZone、栈保护)配合使用。
避坑指南:我曾经在一个项目里发现,Secure Boot 验签通过了,但应用程序运行时却崩溃了。排查了半天,发现是 Bootloader 和应用程序的链接脚本不一致,导致中断向量表偏移错误。所以,记得在跳转前检查中断向量表的有效性。
好了,Secure Boot 的原理和实现就讲到这里。下一章咱们聊聊「固件加密与解密」,看看如何保护固件在存储和传输过程中的机密性。