4、固件加密与签名:对称与非对称加密、固件签名验证机制、常见绕过手法
说到固件安全,加密和签名是绕不开的两道防线。很多人觉得只要把固件加密了,别人就解不开;或者只要签了名,别人就改不了。嗯,现实往往比这复杂得多。我在做安全审计时,见过太多「看似加密实则裸奔」的案例了。
这一章,我们就来聊聊固件加密与签名的底层逻辑。我会结合自己踩过的坑,把对称加密、非对称加密、签名验证机制,以及那些常见的绕过手法,一次性讲清楚。
4.1 对称加密 vs 非对称加密:选哪个?
先搞清楚这两个概念。说白了,对称加密就是加密和解密用同一把钥匙。非对称加密呢,用公钥加密,私钥解密,或者反过来。
对称加密,典型的有 AES、DES、3DES。速度快,适合加密大块数据。但问题也很明显——密钥怎么安全地传给对方?我在一个 IoT 项目里见过,厂商直接把 AES 密钥硬编码在固件里,结果被逆向出来,整个产品线都得召回。
非对称加密,RSA、ECC 是主力。公钥可以公开,私钥必须保密。适合做密钥交换和数字签名。但性能开销大,不适合加密整个固件。
实际项目中,我建议的做法是:混合使用。用非对称加密来保护对称密钥,再用对称密钥加密固件本体。这样既安全,又高效。
| 特性 | 对称加密 (AES) | 非对称加密 (RSA/ECC) |
|---|---|---|
| 密钥数量 | 1 个(共享密钥) | 2 个(公钥 + 私钥) |
| 性能 | 快(硬件加速常见) | 慢(适合小数据) |
| 密钥分发 | 困难(需要安全通道) | 容易(公钥可公开) |
| 典型用途 | 固件加密、数据加密 | 签名、密钥交换 |
| 常见算法 | AES-128/256, ChaCha20 | RSA-2048, ECDSA, Ed25519 |
核心原则:永远不要用对称密钥直接加密固件,除非你能保证密钥永远不会泄露。而密钥一旦硬编码,就等于没加密。
4.2 固件签名验证机制:到底在验证什么?
固件签名,不是用来保密的,而是用来保证完整性和真实性的。你拿到一个固件,怎么知道它是不是官方发布的?有没有被篡改过?签名就是干这个的。
流程大致是这样的:
- 厂商用私钥对固件的哈希值进行签名,生成签名数据。
- 固件发布时,把固件本体 + 签名数据 + 公钥(或公钥证书)一起打包。
- 设备启动时,用公钥解密签名,得到哈希值 A;再计算固件本体的哈希值 B。
- 如果 A == B,说明固件是完整的、未被篡改的,且确实是该公钥对应的私钥签发的。
听起来很完美,对吧?但我在审计中见过太多「签名验证形同虚设」的情况。比如:
- 设备只验证签名是否存在,不验证签名是否有效。
- 公钥硬编码在固件里,但可以被替换。
- 签名验证失败后,设备仍然继续启动。
你想想看,如果签名验证只是走个过场,那跟没签有什么区别?
我的习惯:在审计签名验证逻辑时,我会先看「验证失败」的分支。如果失败后还能正常启动,那基本可以判定为高危漏洞。
4.3 常见绕过手法:攻击者是怎么想的?
攻击者不会按你的剧本走。他们最擅长的,就是找那些「你以为没问题」的角落。我总结了几种最常见的绕过手法,每一个我都曾在真实项目中遇到过。
4.3.1 公钥替换攻击
这是最经典的一招。如果公钥存储在可写介质上(比如 SPI Flash),攻击者可以直接用自己的公钥替换掉原来的公钥。然后用自己生成的私钥对恶意固件签名。设备验证时,用的是攻击者的公钥,自然能通过。
避坑指南:我曾经审计过一个路由器,公钥竟然放在文件系统里,而且没有写保护。攻击者只需要一个 shell 就能替换公钥。正确的做法是把公钥烧录到一次性 OTP 区域,或者用硬件安全模块(HSM)保护。
4.3.2 签名验证逻辑绕过
有些设备的签名验证代码写得非常「随意」。比如:
// 伪代码示例
if (verify_signature(firmware, signature) == SUCCESS) {
boot_firmware(firmware);
} else {
// 什么也不做,继续执行
}
boot_firmware(firmware); // 绕过!
看到了吗?验证失败后,代码没有 return 或 exit,而是直接 fall-through 到了 boot_firmware。攻击者只需要提供一个无效签名,设备照样能启动。
4.3.3 哈希碰撞与算法降级
如果签名算法用的是 MD5 或 SHA-1,那就有哈希碰撞的风险。攻击者可以构造一个与原固件哈希相同的恶意固件。另外,有些设备支持「算法协商」,攻击者可以强制降级到弱算法。
我曾经在一个工控设备上看到,它同时支持 SHA-1 和 SHA-256 签名。攻击者只需要把签名算法字段改成 SHA-1,然后用自己的 SHA-1 签名就能通过验证。嗯,这种设计简直是给攻击者留后门。
4.3.4 时间戳与回滚攻击
签名通常包含时间戳或版本号。如果设备没有强制版本递增检查,攻击者可以刷回一个旧版本的固件,利用已知漏洞进行攻击。这就是回滚攻击。
我建议的做法是:在签名数据中包含版本号,并在验证时检查版本号是否大于等于当前版本。同时,版本号也要签名,防止被篡改。
注意:回滚攻击在 IoT 设备中非常普遍。很多厂商只关注「能不能刷入新固件」,却忘了「能不能刷回旧固件」。旧固件里的漏洞,往往已经被公开了。
4.4 知识体系:固件加密与签名全景图
下面这张图,我把整个固件加密与签名的核心逻辑画了出来。你可以把它当作一个检查清单,审计时对照着看,基本不会漏掉关键点。
4.5 实战建议:如何设计一个靠谱的签名验证流程?
说了这么多绕过手法,那到底该怎么设计?我分享一个我自己在项目中验证过的流程,你可以直接参考:
- 公钥存储在 OTP 或安全元件中,不可写,不可替换。
- 签名算法使用 ECDSA 或 Ed25519,避免 RSA 在嵌入式环境中的性能问题。
- 签名数据包含固件版本号、设备 ID、时间戳,防止回滚和跨设备攻击。
- 验证失败直接进入 bootloader 错误处理,不执行任何后续代码。
- 增加防回滚计数器,每次升级递增,存储在 OTP 区域。
一个小技巧:在签名验证代码中加入「自检」逻辑。每次启动时,先验证签名验证代码本身是否被篡改。虽然会增加启动时间,但能有效防止代码被 patch。