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写入是有一定概率出错的,尤其是在电池电量低的时候。

完整性校验的完整流程:

  1. 接收固件包
  2. 验签(数字签名验证)
  3. 版本号检查
  4. 硬件兼容性检查
  5. 写入Flash
  6. 读出Flash,CRC校验
  7. 如果全部通过,跳转到新固件

你想想看,每一步都是在增加安全性。虽然看起来繁琐,但这些都是血的教训换来的。我早期做的一个项目,就是因为跳过了版本号检查,导致用户反复刷同一个版本的固件,最后Flash被写坏了。

好了,这一节的内容就到这里。数字签名是OTA升级安全的基石,SHA256是当前最稳妥的哈希选择,RSA和ECDSA各有优劣,你需要根据MCU的资源来权衡。最后,完整性校验是一个系统工程,不要只盯着验签。

下一节,我们会聊到安全启动链,也就是如何保证MCU上电后,第一个执行的代码就是可信的。这比固件签名还要底层,咱们到时候细说。