4、数字签名基础:非对称加密(RSA/ECC)原理、签名生成与验证流程、公钥的存储与保护

聊到 BootROM 镜像校验,数字签名是绕不开的核心。说白了,签名就是给固件贴上一张「防伪标签」。这张标签只有私钥持有者能贴,但任何人都能用公钥去验。今天我们就把它拆开揉碎了讲清楚。

4.1 非对称加密:为什么需要两把钥匙?

对称加密就像一把钥匙开一把锁,加密和解密用同一个密钥。这在通信双方事先约定好密钥的场景下没问题。但 BootROM 场景下,设备在产线烧录时,你没法预知每一台设备将来要校验哪个固件版本。更关键的是——密钥怎么安全地分发给所有设备?

非对称加密解决了这个难题。它用一对密钥:私钥(自己藏好)和公钥(公开分发)。私钥签名的数据,只能用对应的公钥验证;反过来,公钥加密的数据,只能用私钥解密。两个方向,用途不同。

核心区别速览

特性 对称加密 非对称加密
密钥数量 1个(共享密钥) 2个(私钥+公钥)
密钥分发 困难,需安全通道 公钥可公开,私钥本地保管
典型算法 AES, DES RSA, ECC
计算开销 低 高(尤其RSA)
BootROM常用场景 加密镜像体(配合签名) 签名验证

我个人习惯在 BootROM 里只用非对称做签名验证,镜像体本身用对称加密。这样既保证了签名不可伪造,又兼顾了解密速度。

4.2 RSA 与 ECC:两种主流方案怎么选?

RSA 和 ECC 是目前 BootROM 签名最常用的两种算法。它们的数学基础不同,但目标一致:让私钥签名容易,让攻击者伪造签名极难。

4.2.1 RSA:大整数分解难题

RSA 的安全性基于「大整数分解」的困难性。给你两个大素数 p 和 q,乘起来得到 n 很容易;但给你 n,反推出 p 和 q 就非常困难。私钥就是基于 p 和 q 算出来的,公钥则包含 n 和一个公开指数 e。

签名过程:对镜像哈希值做私钥指数运算(模幂)。验证过程:用公钥指数运算还原哈希,再比对。

我踩过的坑

RSA 的密钥长度很关键。1024 位已经被认为不安全,我建议至少用 2048 位。但注意,密钥越长,签名和验证的耗时也越长。在 BootROM 这种资源受限的环境下,我曾经遇到过 4096 位 RSA 验证耗时超过 500ms 的情况——这直接拖慢了启动速度。后来换成了 ECC。

4.2.2 ECC:椭圆曲线离散对数难题

ECC 的安全性基于椭圆曲线上的离散对数问题。简单说,给定曲线上一个点 G 和一个大整数 k,计算 k*G 很容易;但给定 G 和 k*G,反推 k 几乎不可能。

ECC 的优势在于:同等安全强度下,密钥长度远小于 RSA。256 位 ECC 密钥的安全性与 3072 位 RSA 相当。这对 BootROM 这种存储和计算资源都受限的场景非常友好。

安全等级 RSA 密钥长度 ECC 密钥长度 签名长度
80 位 1024 160-223 RSA: 128 字节 / ECC: 40-56 字节
112 位 2048 224-255 RSA: 256 字节 / ECC: 56-64 字节
128 位 3072 256-383 RSA: 384 字节 / ECC: 64-96 字节
192 位 7680 384-511 RSA: 960 字节 / ECC: 96-128 字节

你看,ECC 的签名长度也短很多。在 BootROM 里,签名是附加在镜像尾部的,每省一个字节都是实打实的 Flash 空间。

4.3 签名生成与验证流程

流程其实不复杂。我画了一张图,把签名和验证两条路径放在一起对比,你一看就明白。

数字签名生成与验证流程 数字签名生成与验证流程 签名生成(私钥持有者) 原始固件镜像 哈希运算(SHA-256) 私钥加密(RSA/ECC) 生成数字签名 镜像 + 签名打包 签名验证(BootROM) 接收镜像 + 签名 对镜像做哈希运算 公钥解密签名 比对两个哈希值 一致 → 启动 不一致 → 拒绝 注意:签名不是直接加密整个镜像,而是加密镜像的哈希值。这样既保证完整性,又大幅减少计算量。

流程总结起来就三步:

  1. 哈希:对固件镜像做一次哈希运算(比如 SHA-256),得到一个固定长度的摘要。
  2. 签名:用私钥对这个摘要做加密运算,生成数字签名。
  3. 验证:BootROM 收到镜像后,自己算一遍哈希,再用公钥解密签名得到原始哈希,两者比对。

这里有个细节:签名不是直接加密整个镜像。你想想看,一个固件可能几兆甚至几十兆,用私钥做非对称加密,计算量太大了。所以只加密哈希值,既保证了完整性,又大幅减少了计算开销。

4.4 公钥的存储与保护

公钥虽然可以公开,但在 BootROM 场景下,它的存储方式直接决定了整个信任链的安全。如果公钥被篡改,攻击者就能用自己的私钥签名恶意固件,设备照样会「信任」它。

所以公钥必须存储在不可篡改的区域。常见的做法有几种:

存储方式 安全性 灵活性 典型场景
硬编码在 BootROM 只读区 最高(物理不可改) 最低(需重新流片) 一次性烧录的 MCU
存储在 OTP(一次性可编程) 高(烧录后不可改) 中等(产线烧录) 量产设备
存储在 eFuse 高(熔断后不可恢复) 中等(可逐位熔断) 安全芯片、SoC
存储在 Flash 并加签名保护 中等(依赖上层签名) 高(可更新) 支持 OTA 的设备

我曾经踩过的坑

有一次做安全审计,发现某款设备的公钥竟然存储在外部 SPI Flash 里,而且没有做任何完整性保护。攻击者只要物理接触设备,替换 Flash 里的公钥,就能刷入任意固件。这个漏洞的修复方案是把公钥迁移到芯片内部的 OTP 区域,同时增加硬件防回滚机制。

我个人建议:公钥的根信任必须锚定在硬件不可变区域。BootROM 里硬编码一个根公钥哈希,或者直接存根公钥,然后用这个根公钥去验证次级公钥。这样既保证了根信任的绝对安全,又给密钥更新留了余地。

4.5 避坑指南:签名实现中的常见问题

理论讲完了,说几个实际项目中容易翻车的地方。

  • 哈希算法选择:别用 MD5 或 SHA-1,它们已经被证明存在碰撞攻击。至少用 SHA-256。
  • 填充方案:RSA 签名必须使用 PSS 填充,不要用 PKCS#1 v1.5。后者有已知的 Bleichenbacher 攻击变种。
  • 随机数质量:ECC 签名需要高质量的随机数。如果随机数生成器被预测,私钥可能被恢复。我记得有个知名案例就是某游戏机因为随机数问题导致私钥泄露。
  • 防回滚:签名验证通过后,还要检查镜像版本号。否则攻击者可以拿一个旧版本(有已知漏洞)的合法签名镜像来降级攻击。

一个小技巧

在 BootROM 里做签名验证时,我习惯先验证签名,再解密镜像体。这样如果签名不合法,直接拒绝,省了解密的时间和电量。对于电池供电的 IoT 设备,这个顺序能省不少电。

嗯,数字签名的基础就这些。理解了非对称加密的原理、签名流程和公钥保护策略,你就能看懂 BootROM 是如何建立信任根的。下一层,我们会把这些知识串起来,看看整个镜像校验链是怎么设计的。