2、固件安全基线:固件签名验证、安全启动链、固件加密存储、防回滚机制
固件安全,说白了就是设备的“基因安全”。
我见过太多团队,硬件设计得漂漂亮亮,结果固件一上来就被逆向、被篡改、被降级。设备出了厂,就跟裸奔一样。你想想看,如果连跑在芯片上的代码都不安全,那上层做再多应用层防护,都是白搭。
这一章,我们就把固件安全的四个核心基线讲透:签名验证、安全启动、加密存储、防回滚。这四个点,是物联网设备合规的硬门槛,也是我这些年踩坑最多的领域。
2.1 固件签名验证:给固件贴上“防伪标签”
固件签名,本质上就是给二进制文件打一个数字签名。谁签的?设备厂商的私钥。谁验的?设备里的公钥。
我在一个智能门锁项目里遇到过,客户说“我们固件有签名”,结果一看,签名算法用的是 MD5,密钥硬编码在 Flash 里,随便一个调试接口就能读出来。这跟没签有什么区别?
核心原则:
- 签名算法必须用非对称加密,推荐 ECDSA 或 RSA-2048 以上
- 公钥必须存储在芯片内部的一次性写入区域(OTP/eFuse)
- 签名过程必须在安全的生产环境中完成,私钥不出 HSM
具体怎么做?我习惯把签名流程分成三步:
- 生成摘要:对固件二进制做 SHA-256 哈希,得到固定长度的摘要
- 签名:用厂商私钥对摘要加密,生成签名数据
- 打包:将原始固件 + 签名数据 + 证书链打包成一个文件
设备端验证时,用公钥解密签名,跟本地计算的摘要比对。一致,就说明固件没被改过。
我的习惯:签名时一定要带上固件版本号和芯片唯一 ID。这样能防止签名被复制到另一台设备上使用。这叫“防重放攻击”。
2.2 安全启动链:从第一行代码开始信任
安全启动,不是只检查一次。它是从芯片复位开始,每一级代码都要验证下一级。
典型的启动链是这样的:
- ROM Bootloader:芯片出厂固化的代码,不可更改。它验证下一级 Bootloader 的签名
- Bootloader:验证 OS Kernel 的签名
- Kernel:验证文件系统/应用固件的签名
- 应用:运行时验证动态加载模块的签名
每一级验证通过,才把控制权交出去。任何一级失败,设备就进入“恢复模式”或直接死机。
我曾经踩过的坑:有个项目,ROM Bootloader 验证了 Bootloader,但 Bootloader 没验证 Kernel。攻击者直接替换了 Kernel 分区,设备照样启动。记住,安全启动链不能有“断链”。
下面这张图,是我常用的安全启动链结构:
2.3 固件加密存储:别让固件“裸奔”在 Flash 里
签名防篡改,加密防泄露。这两个是不同的事。
我见过不少设备,固件在 Flash 里是明文存储的。攻击者用个编程器,把 Flash 拆下来一读,整个固件就拿到了。然后逆向分析,找漏洞,改代码,再写回去。设备就成别人的了。
固件加密存储,通常有两种做法:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 全盘加密 | 整个 Flash 用 AES 密钥加密,启动时由 Bootloader 解密 | 安全性高,实现简单 | 启动慢,密钥管理复杂 |
| 分区加密 | 只加密敏感分区(如文件系统、配置区) | 性能影响小,灵活 | 需要仔细规划分区表 |
| 按需解密 | 运行时只解密正在执行的代码页 | 内存占用低,抗侧信道 | 实现复杂,需要 MMU 支持 |
我建议:对于资源受限的 MCU,优先用分区加密。把 Bootloader 和关键配置区加密,应用代码可以明文(但必须签名)。这样平衡了安全和性能。
密钥怎么存?嗯,这里要注意:
- 绝对不能把密钥明文写在固件里
- 利用芯片的硬件安全模块(HSM/SE),密钥生成在芯片内部
- 或者用物理不可克隆函数(PUF),每个芯片的密钥都不同
2.4 防回滚机制:堵住“降级攻击”的漏洞
防回滚,很多人会忽略。但攻击者最喜欢这个。
为什么?因为新固件修了漏洞,但旧固件有漏洞。攻击者把设备固件降级到有漏洞的版本,然后利用已知漏洞入侵。这就是“降级攻击”。
我在一个摄像头项目里就遇到过。固件签名验证做得很好,但没做版本检查。攻击者把固件降级到三年前的版本,那个版本有个缓冲区溢出漏洞,直接 getshell 了。
防回滚的核心机制:
- 固件头里必须包含版本号,且版本号单调递增
- Bootloader 在验证签名前,先检查新固件版本号是否 >= 当前版本
- 版本号存储在一次性写入区域(如 eFuse),升级后不可回退
- 或者用“版本计数器”,每次升级加一,存储在安全存储区
具体实现时,我习惯用“版本槽位”的方式:
// 固件头结构示例
typedef struct {
uint32_t magic; // 魔数,用于识别固件格式
uint32_t version; // 版本号,单调递增
uint32_t chip_id; // 芯片唯一ID
uint32_t firmware_size; // 固件大小
uint8_t hash[32]; // SHA-256 摘要
uint8_t signature[256]; // ECDSA 签名
} firmware_header_t;
// 版本检查逻辑
if (new_fw->version <= current_version) {
// 拒绝降级
return ERROR_ROLLBACK_DETECTED;
}
避坑指南:
我曾经犯过一个错:版本号用字符串比较(如 "v2.1.3" > "v2.1.10" 会返回 false)。后来统一改成整数版本号,或者用语义化版本解析库。记住,版本比较必须用数值,不能用字符串。
2.5 四个基线的联动关系
这四个基线不是孤立的。它们是一个整体:
- 签名验证是入口,确保固件来源可信
- 安全启动是链条,确保每一级代码都可信
- 加密存储是屏障,防止固件被静态分析
- 防回滚是兜底,防止攻击者利用旧版本漏洞
少了任何一个,整个安全体系就有缺口。我见过太多合规审计,就是因为防回滚没做,被判定为“严重不合规”。