3. 固件镜像的签名验证:非对称加密(RSA、ECDSA)在固件签名中的应用
说到固件安全,签名验证是绕不开的一环。你想想看,如果设备随便拿个镜像就往里刷,那跟把家门钥匙挂在门外有什么区别?我这些年做过的项目里,因为签名没做好导致设备被篡改的案例,一只手都数不过来。
非对称加密,说白了就是一对钥匙——私钥签名,公钥验签。私钥你死死攥在手里,公钥可以公开给全世界。这个机制天然适合固件升级场景:开发者在服务器上用私钥给固件签名,设备端用预置的公钥去验证。只要私钥不泄露,谁也别想伪造你的固件。
3.1 为什么非对称加密更适合固件签名?
对称加密也能做校验,比如 HMAC。但有个致命问题——密钥必须共享。你想想,如果每台设备里都藏着同一个密钥,一旦某台设备被破解,整个产品线的密钥就全废了。我在一个 IoT 项目里就吃过这个亏,当时为了省成本用了 HMAC,结果被逆向出来,所有设备都得返厂升级。
非对称加密就不一样了:
- 私钥:只保存在安全的签名服务器上,永远不离开
- 公钥:烧录在每台设备的只读存储区,公开也没关系
- 签名:用私钥对固件哈希加密,任何人都能用公钥验证
这个模型的好处是,即使设备被物理破解,攻击者拿到的也只是公钥,无法伪造签名。最多只能验证签名,但造不出新的合法固件。
核心原则:签名 ≠ 加密。签名是为了证明「谁写的」,加密是为了「不让别人看」。固件升级里我们只关心前者——确保镜像来自可信源。
3.2 RSA vs ECDSA:怎么选?
目前主流方案就两个:RSA 和 ECDSA。我两个都用过,各有各的脾气。
| 对比项 | RSA | ECDSA |
|---|---|---|
| 密钥长度 | 2048 / 4096 位 | 256 / 384 位 |
| 签名大小 | 256 / 512 字节 | 64 / 96 字节 |
| 验签速度 | 较慢(指数运算) | 较快(椭圆曲线点乘) |
| 硬件支持 | 几乎所有 MCU 都有硬件加速 | 较新芯片才支持 |
| 安全性 | 成熟,量子计算前够用 | 同等安全强度下密钥更短 |
我个人习惯:如果芯片有硬件 ECC 引擎,优先用 ECDSA。签名小、速度快,对资源受限的嵌入式设备很友好。但如果芯片比较老,或者你不想依赖硬件加速,RSA 2048 仍然是稳妥的选择。
经验之谈:别为了省空间用 RSA 1024。我在 2019 年做过一个项目,客户坚持用 1024 位,结果第二年就被学术界攻破了 768 位的 RSA。虽然 1024 还没完全沦陷,但安全余量已经不够了。现在底线是 2048 位。
3.3 签名验证的完整流程
嗯,这里要注意,签名不是对整个固件做非对称加密——那样太慢了。实际做法是:
- 计算哈希:对固件镜像做 SHA-256,得到一个 32 字节的摘要
- 签名:用私钥对这个摘要做签名运算,生成签名数据
- 打包:把签名数据附加到固件镜像后面,或者单独放在一个签名文件中
- 验签:设备端先计算本地固件的哈希,再用公钥验证签名是否匹配
下面这张图把流程画清楚了:
3.4 代码示例:用 OpenSSL 做 ECDSA 签名与验证
光说不练假把式。我贴一段实际项目里用过的脚本,帮你理解签名和验签到底怎么操作。这里用 ECDSA 曲线 P-256:
# 1. 生成 ECDSA 密钥对(P-256 曲线)
openssl ecparam -genkey -name prime256v1 -out private.pem
# 2. 导出公钥
openssl ec -in private.pem -pubout -out public.pem
# 3. 计算固件哈希并签名
openssl dgst -sha256 -sign private.pem -out firmware.sig firmware.bin
# 4. 验证签名(设备端操作)
openssl dgst -sha256 -verify public.pem -signature firmware.sig firmware.bin
# 如果输出 "Verified OK",说明固件合法
实际嵌入式代码里,你不可能跑 OpenSSL 命令行。一般是用 mbedTLS 或 OpenSSL 库的 API。核心逻辑就三步:
- mbedtls_pk_parse_public_key():加载公钥
- mbedtls_pk_verify():传入哈希、签名、公钥,返回 0 表示验证通过
- mbedtls_sha256_ret():计算固件哈希
曾经踩过的坑:有一次我把签名数据放在固件镜像末尾,验签时把整个镜像包括签名一起算了哈希。结果永远验不过。记住——签名不能参与哈希计算。正确的做法是:先算固件本身的哈希,再验证签名。签名数据是附加的,不是固件的一部分。
3.5 公钥的存储与防篡改
公钥虽然可以公开,但你不能让它被随便替换。如果攻击者把设备里的公钥换成自己的,那他就能用自己的私钥签名恶意固件,设备照样认。
我建议的做法:
- 烧录在一次性可编程(OTP)区域:比如 MCU 的 eFuse,写进去就改不了
- 使用安全元件(SE):公钥存在独立的安全芯片里,主控只能调用验证接口,拿不到原始数据
- 代码中硬编码 + 校验:如果硬件不支持 OTP,至少把公钥的哈希也存一份,启动时自检
一个小技巧:公钥可以拆成多段分散存储,启动时再拼接。虽然不能完全防住专业攻击,但能增加逆向成本。我在一个消费类产品上用过这招,至少拖慢了破解者两周时间。
3.6 签名验证的常见陷阱
做了这么多年固件安全,我总结了几条高频翻车点:
- 哈希算法不匹配:签名时用 SHA-256,验签时用了 SHA-1,结果永远对不上。统一用 SHA-256 就好。
- 字节序问题:大端小端搞反了。尤其是 ECDSA 签名,R 和 S 值的顺序、字节序都必须严格一致。
- 填充方式:RSA 签名有 PKCS#1 v1.5 和 PSS 两种填充。两边必须用同一种。我建议用 PSS,安全性更高。
- 忽略签名格式:OpenSSL 默认输出 DER 格式,有些库要求原始 R||S 格式。转换一下就好。
一句话总结:签名验证不是玄学,是工程。把流程理清楚,把边界条件测全,你的固件升级就安全了一大半。