2. 固件签名原理:哈希、非对称加密与数字签名
这一章,我们来聊聊固件签名的核心原理。说白了,就是回答一个问题:你怎么证明你手里的固件是“真”的,而且没被人动过手脚?
我在做嵌入式安全的头几年,见过太多产品因为没做签名校验,被人在 Flash 里塞了后门。嗯,那滋味可不好受。所以,理解签名原理,是固件安全的第一道防线。
2.1 哈希算法:固件的“指纹”
先说说哈希。哈希算法,比如 SHA256,它干的事很简单:不管你的固件有多大——几十 KB 也好,几 MB 也好——它都能算出一个固定长度的“摘要”。这个摘要,就像人的指纹一样,是唯一的。
哈希算法的两个关键特性:
- 不可逆:从摘要反推原始数据,理论上不可能。你只能正向算,不能反向解。
- 抗碰撞:几乎不可能找到两个不同的固件,算出相同的 SHA256 值。我见过有人想用 MD5 做签名,结果被碰撞攻击搞得焦头烂额。所以,现在业界标准就是 SHA256。
举个例子,你写了一段固件,内容是 Hello, World!。用 SHA256 算出来是一串 64 位的十六进制数。哪怕你只改了一个字母,改成 Hello, world!,算出来的哈希值就完全不一样了。这就是哈希的“雪崩效应”。
我的习惯:在项目里,我通常会在固件编译完成后,自动计算一次 SHA256 值,并把它记录在构建日志里。这样,后续做版本追溯时,能快速确认固件是否被意外修改过。
2.2 非对称加密:公钥与私钥的“锁与钥匙”
哈希能保证完整性,但没法保证“谁”生成的。这时候就需要非对称加密了。它有两把钥匙:一把私钥(自己藏着),一把公钥(公开出去)。
常见的算法有两种:RSA 和 ECC。我个人的经验是,如果芯片资源紧张,优先选 ECC。为什么?因为 ECC 在同等安全强度下,密钥更短,计算更快。比如,256 位的 ECC 密钥,安全性和 3072 位的 RSA 差不多。
| 特性 | RSA | ECC |
|---|---|---|
| 密钥长度(同等安全) | 2048 / 3072 位 | 224 / 256 位 |
| 签名速度 | 较慢 | 较快 |
| 验签速度 | 较快 | 较慢(但可接受) |
| 资源占用 | 高 | 低 |
| 典型应用 | PC / 服务器端 | 嵌入式 / IoT 设备 |
避坑指南:我曾经在一个项目里,为了省成本,用了 1024 位的 RSA。结果产品上市不到一年,就被爆出密钥可被暴力破解。从那以后,我坚持:嵌入式设备至少用 2048 位 RSA 或 256 位 ECC。别在这上面省钱,不值得。
2.3 数字签名的生成与验证流程
好了,把哈希和非对称加密结合起来,就是数字签名。流程其实不复杂,我画了一张图,你一看就明白。
流程其实就两步:
- 签名生成(在安全环境,比如你的编译服务器上):
- 对固件算 SHA256,得到摘要 H。
- 用私钥对 H 加密,得到签名 S。
- 把固件和签名打包在一起,发布出去。
- 签名验证(在设备上,比如 MCU 启动时):
- 设备收到固件后,先对固件本体算 SHA256,得到 H1。
- 用公钥解密签名 S,得到 H2。
- 比较 H1 和 H2。如果相等,固件就是可信的;否则,拒绝启动或升级。
为什么这样设计?你想想看,如果直接对固件本体做非对称加密,那计算量太大了。哈希算法很快,先把固件“浓缩”成一小段摘要,再对摘要签名,效率高得多。我在一个 STM32 项目上实测过,SHA256 算 1MB 固件大概只要几十毫秒,而 RSA 2048 签名一次要几百毫秒。所以,先哈希再签名,是工程上的最优解。
2.4 实际项目中的注意事项
理论说完了,聊点实战中容易踩的坑。
- 密钥管理:私钥一定要放在离线环境,比如专用的 HSM(硬件安全模块)或加密的 U 盘里。我见过有人把私钥直接放在 Git 仓库里,结果被内部员工泄露。嗯,那场面,很尴尬。
- 公钥存储:公钥要烧死在芯片的 OTP(一次性可编程)区域,或者放在安全存储区。如果公钥能被篡改,那签名就形同虚设。
- 签名算法选择:如果芯片支持硬件加速,优先用硬件支持的算法。比如,有些芯片内置了 ECC 加速器,用起来比纯软件快 10 倍以上。
我曾经踩过的坑:有一次,我在做 OTA 升级时,忘了在 Bootloader 里做签名校验。结果测试时发现,只要有人抓包篡改固件,设备就能被刷入恶意代码。从那以后,我定了个规矩:所有 Bootloader 和固件升级流程,必须强制校验签名,否则不允许启动。