2、固件签名基础:非对称加密原理、哈希算法、数字签名生成与验证流程

好,咱们正式开始聊固件签名。说实话,这是整个可信启动链条里最核心的一环。你想想看,如果没有签名机制,那所谓的「安全启动」就是个空壳子——谁都能往你的 Flash 里写一段恶意代码,然后设备照跑不误。

我刚开始做嵌入式安全那会儿,也踩过不少坑。有一次给一款 IoT 网关做固件升级,没做签名校验,结果客户反馈设备频繁变砖。后来一查,是有人在传输过程中篡改了固件包。嗯,从那以后,我再也不敢跳过签名这一步了。

这一章,咱们就把签名背后的三个基础概念掰开揉碎讲清楚:非对称加密、哈希算法、以及数字签名的生成与验证流程。别怕数学,咱们用工程思维来理解它。

2.1 非对称加密:公钥与私钥的「锁与钥匙」

非对称加密,说白了就是两把钥匙:一把公开(公钥),一把私藏(私钥)。公钥加密的数据,只有私钥能解开;反过来,私钥签名的数据,只有公钥能验证。

我习惯用一个比喻来理解:私钥是你的个人印章,公钥是印鉴样本。你拿印章盖个戳(签名),别人拿印鉴样本对照(验证),就知道这个戳是不是你盖的。

常见的非对称算法有 RSA 和 ECC。咱们做固件签名,选哪个?看场景。

算法 密钥长度(推荐) 签名长度 计算开销 典型应用
RSA 2048 / 4096 bit 与密钥长度相同 较高(尤其私钥操作) PC、服务器、高性能 MCU
ECDSA 256 / 384 bit 约 64 / 96 字节 较低 嵌入式、IoT、资源受限设备

我个人更倾向 ECDSA,尤其是在资源受限的 MCU 上。签名短、算得快,而且同等安全强度下密钥长度比 RSA 短得多。你想想看,一个 256 位的 ECC 密钥,安全性和 3072 位的 RSA 差不多,但签名体积小了好几倍——这对固件包大小敏感的场景来说,太重要了。

避坑指南: 我曾经在一个项目里用了 RSA 4096,结果发现目标 MCU 的硬件加速器只支持到 2048。软件跑 4096 的签名验证,耗时超过 2 秒,直接导致启动超时。所以选算法前,一定先查芯片的硬件加速能力。

2.2 哈希算法:固件的「数字指纹」

非对称加密有个问题:它处理不了大块数据。固件动辄几百 KB 甚至几 MB,用私钥直接对整个固件做签名,计算量巨大,而且签名结果也会和固件一样长——这显然不现实。

所以我们需要哈希算法。哈希函数能把任意长度的数据,压缩成一个固定长度的摘要(digest)。这个摘要就像固件的「数字指纹」:只要固件内容变了哪怕一个 bit,指纹就会完全不一样。

常用的哈希算法有 SHA-256 和 SHA-384。我建议至少用 SHA-256,SHA-1 已经不安全了,别碰。

核心要点: 哈希是单向的。你从固件算出哈希很容易,但从哈希反推出固件内容,理论上不可能。这是签名安全的基础。

实际项目中,我们不会对整个固件直接签名,而是:

  1. 对固件计算哈希值(比如 SHA-256)
  2. 对哈希值进行签名(用私钥加密哈希值)
  3. 把签名结果附加到固件包中

验证时,接收方用公钥解密签名得到哈希值,再自己算一遍固件的哈希,两者对比。一致,则固件未被篡改;不一致,则拒绝启动。

2.3 数字签名生成与验证流程

好,现在我们把上面两个概念串起来,看看完整的签名流程长什么样。

下面这张图是我自己画的一个流程框架,你可以把它当作签名操作的「标准操作程序」。

数字签名生成与验证流程 签名生成(签名者) 原始固件(Firmware.bin) SHA-256 哈希计算 哈希值(32 字节摘要) 私钥加密 → 数字签名 固件包 = 固件 + 签名 签名验证(验证者) 接收固件包 SHA-256 计算固件哈希 计算出的哈希值(H1) 公钥解密签名 → 哈希值(H2) 比较 H1 == H2 ? 传输 H1 == H2 → 验证通过,固件未被篡改;H1 ≠ H2 → 拒绝启动

流程其实不复杂,但有几个细节我特别想强调一下。

2.3.1 签名生成阶段(你作为固件发布者)

  • 步骤一: 编译出固件二进制文件。嗯,这一步通常 CI/CD 自动完成。
  • 步骤二: 对固件计算 SHA-256 哈希。得到 32 字节的摘要。
  • 步骤三: 用你的私钥对这个摘要做签名(RSA 或 ECDSA)。输出就是数字签名。
  • 步骤四: 把原始固件和签名打包在一起。有些方案还会加上固件版本、设备 ID 等元数据。
重要提醒: 私钥必须离线保管!我见过有人把私钥直接放在 CI 服务器的环境变量里,结果 CI 被攻破,私钥泄露,所有已发布的固件都得重新签名。私钥应该放在 HSM(硬件安全模块)或专用的签名服务器上,永远不要出现在开发机或代码仓库里。

2.3.2 签名验证阶段(设备端 BootROM 或 Bootloader)

  • 步骤一: BootROM 从 Flash 中读取固件包,分离出固件本体和签名。
  • 步骤二: 对固件本体计算 SHA-256 哈希,得到 H1。
  • 步骤三: 用预置在芯片 OTP(一次性可编程存储器)中的公钥,解密签名,得到 H2。
  • 步骤四: 比较 H1 和 H2。一致则跳转到固件入口执行;不一致则进入错误处理(比如死循环或恢复模式)。

这里有个关键点:公钥是怎么安全地放到芯片里的? 我习惯的做法是在芯片出厂时,通过安全烧录工具把公钥哈希写入 OTP。BootROM 里硬编码了公钥哈希的校验逻辑,确保只有经过授权的公钥才能被使用。这样即使攻击者物理接触了芯片,也无法替换公钥。

2.4 实际工程中的常见坑

讲完了理论,咱们聊聊实战中容易翻车的地方。我列几个自己踩过的坑,你遇到了可以少走弯路。

坑 现象 根因 解决方案
哈希计算范围不一致 签名验证总失败 签名时算的哈希范围(比如包含头部),验证时没包含 统一约定:只对固件有效载荷做哈希,头部单独处理
字节序问题 签名验证偶尔失败 MCU 是小端,签名工具是大端,哈希值字节序反了 在签名工具和 BootROM 中显式指定字节序转换
填充模式不匹配 RSA 签名验证失败 签名用了 PKCS#1 v1.5,验证用了 PSS 统一使用 PSS(更安全),且确认双方参数一致
公钥存储位置不当 公钥被篡改,恶意固件通过验证 公钥放在可写的 Flash 区域 公钥必须烧录在 OTP 或 eFuse 中,一次性写入
个人经验: 我建议在开发阶段就写一个自动化测试脚本,每次编译后自动做一遍「签名→验证→启动」的完整流程。别等到集成测试才发现签名逻辑有问题。我曾经因为字节序问题折腾了两天,最后发现是签名工具和 BootROM 的 SHA-256 实现用了不同的字节序——这种低级错误,自动化测试一跑就能抓到。

2.5 小结

这一章的内容,说白了就是三句话:

  • 非对称加密 提供了「私钥签名,公钥验证」的能力。
  • 哈希算法 把大固件压缩成固定长度的摘要,让签名变得可行。
  • 数字签名 就是「对哈希值做非对称加密」的结果,验证时对比两个哈希值是否一致。