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 固件签名验证机制:到底在验证什么?

固件签名,不是用来保密的,而是用来保证完整性和真实性的。你拿到一个固件,怎么知道它是不是官方发布的?有没有被篡改过?签名就是干这个的。

流程大致是这样的:

  1. 厂商用私钥对固件的哈希值进行签名,生成签名数据。
  2. 固件发布时,把固件本体 + 签名数据 + 公钥(或公钥证书)一起打包。
  3. 设备启动时,用公钥解密签名,得到哈希值 A;再计算固件本体的哈希值 B。
  4. 如果 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 知识体系:固件加密与签名全景图

下面这张图,我把整个固件加密与签名的核心逻辑画了出来。你可以把它当作一个检查清单,审计时对照着看,基本不会漏掉关键点。

固件加密与签名核心逻辑 固件加密(对称加密) AES-256 加密固件 密钥由非对称加密保护 密钥硬编码 → 高危 固件签名(非对称加密) 私钥签名 → 公钥验证 验证失败必须拒绝启动 公钥可替换 → 高危 常见绕过手法 公钥替换攻击 签名验证逻辑绕过 哈希碰撞 / 算法降级 回滚攻击 审计要点 1. 密钥存储位置:OTP / HSM / 安全元件? 2. 签名验证失败后的行为:拒绝启动还是继续? 3. 是否支持算法降级?版本号是否被签名?

4.5 实战建议:如何设计一个靠谱的签名验证流程?

说了这么多绕过手法,那到底该怎么设计?我分享一个我自己在项目中验证过的流程,你可以直接参考:

  1. 公钥存储在 OTP 或安全元件中,不可写,不可替换。
  2. 签名算法使用 ECDSA 或 Ed25519,避免 RSA 在嵌入式环境中的性能问题。
  3. 签名数据包含固件版本号、设备 ID、时间戳,防止回滚和跨设备攻击。
  4. 验证失败直接进入 bootloader 错误处理,不执行任何后续代码。
  5. 增加防回滚计数器,每次升级递增,存储在 OTP 区域。

一个小技巧:在签名验证代码中加入「自检」逻辑。每次启动时,先验证签名验证代码本身是否被篡改。虽然会增加启动时间,但能有效防止代码被 patch。