2. 固件签名与验签机制:数字签名原理,哈希算法选择(SHA256),RSA/ECDSA签名流程,固件完整性校验
各位同学,咱们接着聊。上一节我们把OTA升级的整个框架搭起来了,这一节要啃的,是整个安全体系里最核心的一块——固件签名与验签。
说白了,就是给你的固件打个「防伪标签」。你想想看,助听器通过无线方式接收新固件,如果这个固件是黑客伪造的,或者传输过程中被人篡改了一点点,那后果不堪设想。轻则死机,重则可能损坏硬件,甚至变成窃听器。嗯,这绝不是危言耸听。
2.1 数字签名原理:为什么需要它?
数字签名,本质上是一种非对称加密的逆用。我习惯用一个比喻来解释:
- 私钥 = 你的私人印章,只有你自己有。
- 公钥 = 你的公开签名样本,谁都可以拿去看。
- 签名过程 = 你用私钥在固件上「盖个章」。
- 验签过程 = 别人用你的公钥来验证这个章是不是真的。
这里有个关键点:私钥签名,公钥验签。私钥一旦泄露,整个安全体系就崩塌了。我在项目中见过有人把私钥直接放在代码仓库里,当时我就惊了——这相当于把家门钥匙挂在门口啊。
核心原则:私钥必须保存在离线、物理隔离的环境中。公钥可以随固件一起发布,但必须确保其完整性。
2.2 哈希算法选择:为什么是SHA256?
直接对整个固件进行非对称加密签名,计算量太大了。助听器的MCU性能有限,你想想看,一个几兆的固件,用RSA-2048去签名,那得算到猴年马月去?
所以,我们引入哈希算法。先对固件计算一个摘要(也叫哈希值),然后只对这个摘要进行签名。验签时,同样先计算固件的哈希值,再验证签名。
哈希算法有很多,MD5、SHA1、SHA256、SHA3等等。为什么我推荐SHA256?
- MD5和SHA1:已经被证明存在碰撞攻击,不安全了。别用,真的别用。
- SHA256:目前公认的安全强度足够,计算效率也高。在Cortex-M4级别的MCU上,硬件加速下算一个256KB的固件,大概几十毫秒,完全可以接受。
- SHA3:更新,但硬件支持不如SHA256广泛。我个人习惯,在没有特殊要求时,首选SHA256。
避坑指南:我曾经在一个项目里,为了省那一点点算力,用了SHA1。结果客户的安全审计直接不通过,最后全部返工。从那以后,我再也不敢在哈希算法上偷懒了。
2.3 RSA vs ECDSA:签名流程对比
选定了哈希算法,接下来要选签名算法。目前主流的就是RSA和ECDSA。我直接给你对比一下:
| 特性 | RSA (如RSA-2048) | ECDSA (如ECDSA-P256) |
|---|---|---|
| 安全性 | 成熟,广泛验证 | 同等安全强度下,密钥更短 |
| 签名速度 | 较慢(私钥操作) | 较快 |
| 验签速度 | 较快(公钥操作) | 较慢 |
| 密钥长度 | 2048位(256字节) | 256位(32字节) |
| 签名长度 | 256字节 | 64字节 |
| MCU资源占用 | 较高(大数运算) | 较低 |
你可能会问:「那到底选哪个?」
我的建议是:如果MCU算力足够,且你有硬件加速器,RSA-2048是稳妥之选。如果MCU资源紧张,或者你需要更快的签名速度,ECDSA-P256是更好的选择。
我个人在助听器项目里,更倾向于ECDSA。为什么?因为助听器的MCU通常算力有限,而且固件包本身不大,ECDSA的签名长度更短,传输效率也更高。
2.3.1 RSA签名流程示例
咱们来看一个具体的RSA签名流程。假设你有一个固件文件 firmware.bin:
// 签名端(PC/服务器)
1. 计算固件哈希:SHA256(firmware.bin) -> hash (32字节)
2. 使用RSA私钥对hash进行签名:RSA_Sign(private_key, hash) -> signature (256字节)
3. 打包:firmware.bin + signature + 元数据 -> 升级包
// 验签端(助听器MCU)
1. 接收升级包,提取firmware.bin和signature
2. 计算固件哈希:SHA256(firmware.bin) -> hash' (32字节)
3. 使用RSA公钥验签:RSA_Verify(public_key, hash', signature) -> 成功/失败
4. 如果失败,拒绝升级,记录错误日志
注意:验签时,一定要使用公钥,而不是私钥。公钥可以存储在MCU的只读区域,或者通过安全启动链来保证其可信性。
2.3.2 ECDSA签名流程示例
ECDSA的流程和RSA类似,但细节不同:
// 签名端
1. 计算固件哈希:SHA256(firmware.bin) -> hash (32字节)
2. 使用ECDSA私钥对hash进行签名:ECDSA_Sign(private_key, hash) -> signature (64字节,由r和s组成)
3. 打包:firmware.bin + signature + 元数据 -> 升级包
// 验签端
1. 接收升级包,提取firmware.bin和signature
2. 计算固件哈希:SHA256(firmware.bin) -> hash' (32字节)
3. 使用ECDSA公钥验签:ECDSA_Verify(public_key, hash', signature) -> 成功/失败
嗯,这里要注意,ECDSA的签名是一个随机化的过程。每次签名,即使是对同一个固件,生成的签名值也是不同的。这是正常的,不是bug。
2.4 固件完整性校验:不止是验签
验签通过,就代表固件是完整的、可信的吗?
不一定。验签只能证明这个固件是由持有私钥的人签名的,并且没有被篡改。但它不能保证固件本身没有逻辑错误,或者版本号是正确的。
所以,我建议在验签之后,还要做以下几件事:
- 版本号检查:新固件的版本号必须高于当前固件的版本号。防止回滚攻击。
- 硬件兼容性检查:确认这个固件是为当前硬件版本编译的。我曾经遇到过,把A型号的固件刷到B型号上,结果麦克风不工作了。
- CRC校验:在固件写入Flash之后,再读出来算一遍CRC,确保写入过程没有出错。Flash写入是有一定概率出错的,尤其是在电池电量低的时候。
完整性校验的完整流程:
- 接收固件包
- 验签(数字签名验证)
- 版本号检查
- 硬件兼容性检查
- 写入Flash
- 读出Flash,CRC校验
- 如果全部通过,跳转到新固件
你想想看,每一步都是在增加安全性。虽然看起来繁琐,但这些都是血的教训换来的。我早期做的一个项目,就是因为跳过了版本号检查,导致用户反复刷同一个版本的固件,最后Flash被写坏了。
好了,这一节的内容就到这里。数字签名是OTA升级安全的基石,SHA256是当前最稳妥的哈希选择,RSA和ECDSA各有优劣,你需要根据MCU的资源来权衡。最后,完整性校验是一个系统工程,不要只盯着验签。
下一节,我们会聊到安全启动链,也就是如何保证MCU上电后,第一个执行的代码就是可信的。这比固件签名还要底层,咱们到时候细说。