4、固件签名与验证:使用设备证书对固件进行数字签名,设备端验签流程
好,咱们进入正题。上一章我们把设备证书的体系搭起来了,那这证书到底怎么用?最核心的一个场景,就是固件签名与验证。
说白了,就是给固件打个「防伪标签」。我经常跟团队里的小朋友讲:没有签名的固件,就像没贴封条的快递——谁都能拆,谁都能往里塞东西。你想想看,这多可怕。
4.1 为什么必须签名?
OTA 升级的本质,是把新固件从云端推到设备上。但网络是不安全的。中间人攻击、固件篡改、回滚攻击……这些威胁真实存在。
我在一个车联网项目里就遇到过:某次测试,攻击者截获了升级包,把里面的 bootloader 换成了恶意版本。幸好我们的验签机制拦住了,不然整批车都得回厂。嗯,从那以后,我对签名这件事就特别较真。
签名能解决三个核心问题:
- 完整性:固件在传输过程中有没有被改过?
- 真实性:这个固件是不是官方发布的?
- 不可否认性:发布方不能抵赖说「这不是我发的」。
核心原则:设备只信任由合法私钥签名的固件。公钥内置于设备信任根中,不可篡改。
4.2 签名流程:在安全环境中完成
签名这件事,必须在离线、安全的环境里做。我个人习惯用一台不联网的专用签名机,所有私钥都放在 HSM(硬件安全模块)里。
流程其实不复杂,我拆成几步讲:
- 计算固件哈希:对固件二进制做 SHA-256 或 SHA-384 哈希,得到一个固定长度的摘要。
- 私钥签名:用设备证书对应的私钥,对这个哈希值做 RSA 或 ECDSA 签名。
- 打包:把原始固件 + 签名值 + 证书链(可选)打包成一个升级包。
这里有个细节:签的是哈希,不是整个固件。为什么?因为非对称签名很慢,固件动不动几十兆,直接签不现实。哈希一下,效率就上来了。
# 伪代码示例:签名过程
hash = SHA256(firmware.bin)
signature = RSA_SIGN(private_key, hash)
update_package = firmware.bin + signature + device_cert.der
我的经验:签名算法推荐 ECDSA 而不是 RSA。同样的安全强度,ECDSA 的签名更短,计算更快。对资源受限的 IoT 设备来说,这很重要。
4.3 验签流程:设备端的生死关卡
设备收到升级包后,验签是第一道门槛。验签没过,后面的解压、刷写统统不执行。
验签流程是这样的:
- 提取签名和固件:从升级包里拆出原始固件和签名值。
- 获取公钥:从设备的安全存储区读取根证书或设备证书的公钥。
- 计算固件哈希:对收到的固件重新计算 SHA-256 哈希。
- 验签:用公钥验证签名值是否匹配这个哈希。
- 决策:验签通过 → 继续升级;验签失败 → 丢弃升级包,记录日志。
# 伪代码示例:验签过程
firmware, signature = extract(update_package)
hash = SHA256(firmware)
public_key = read_from_secure_storage("device_cert_pub")
if RSA_VERIFY(public_key, hash, signature) == TRUE:
proceed_with_update()
else:
reject_update()
log_error("Signature verification failed")
注意:验签必须在设备的安全执行环境(如 TEE 或 Secure Boot 阶段)中完成。如果在普通操作系统里验签,攻击者可以 hook 验签函数,让它永远返回「通过」。
4.4 证书链验证:不只是验签
光验签还不够。你想想,如果攻击者用自己的证书签名固件,设备用内置的公钥去验,那肯定验不过。但万一设备里的公钥被换了呢?
所以,完整的验证包括证书链验证:
- 设备内置根证书(自签名的信任锚点)。
- 升级包里携带设备证书和中间证书。
- 设备用根证书验证中间证书,再用中间证书验证设备证书,最后用设备证书的公钥验签。
这就像查户口:你不仅要看身份证(设备证书),还要看发证机关(中间 CA)的资质,最终追溯到公安部(根 CA)。
| 验证层级 | 验证内容 | 验证方式 |
|---|---|---|
| 根证书 | 自验证,信任锚点 | 预置于设备 ROM / eFuse |
| 中间证书 | 由根证书签发 | 用根证书公钥验签 |
| 设备证书 | 由中间证书签发 | 用中间证书公钥验签 |
| 固件签名 | 由设备证书私钥签名 | 用设备证书公钥验签 |
4.5 整体流程可视化
下面这张图把签名和验签的完整流程串起来了。左边是签名端(安全环境),右边是验签端(设备端)。
4.6 避坑指南
做固件签名验证,有几个坑我踩过,分享给你:
- 哈希算法别用太弱的:MD5、SHA-1 已经不安全了。最低 SHA-256,我建议 SHA-384。
- 签名值要防重放:在固件里嵌入版本号和随机数,防止攻击者拿旧版本的合法签名包来攻击。
- 验签失败的处理要谨慎:不要只是打印一条日志就继续。我见过一个设备,验签失败后竟然还尝试解压——这等于给攻击者留了后门。
- 公钥存储要防篡改:公钥如果存在可写的 Flash 里,攻击者就能换掉它。必须放在一次性写入的区域,比如 eFuse 或 OTP。
我曾经踩过的坑:有一次,验签逻辑里用了 memcmp 来比较签名值。结果被时序攻击破解了——攻击者通过测量比较时间,逐字节猜出了签名。后来我改用恒定时间比较函数,才堵住这个漏洞。
4.7 小结
固件签名验证,是 OTA 安全的基石。没有它,其他安全措施都是空中楼阁。
记住几个关键点:
- 签名在安全环境做,私钥永不泄露。
- 验签在设备端必须早做、快做、做对。
- 证书链验证不能省,信任必须可追溯。
- 防重放、防时序攻击、防公钥篡改——一个都不能少。