3. 安全威胁模型
好,我们接着聊密码学。上一章我们把对称加密、哈希、非对称加密这些基础概念过了一遍。这一章,咱们得把这些东西串起来,看看它们在实际的固件签名场景里到底是怎么落地的。
说白了,光有 RSA 或者 ECC 算法是不够的。你拿着一个公钥,怎么证明这个公钥真的是你声称的那个设备厂商的?而不是中间人塞给你的假货?这就是 PKI 要解决的问题。
公钥基础设施(PKI)——信任的锚点
PKI 不是一个算法,它是一整套的规则、流程、硬件和软件的集合。我习惯把它理解成「数字世界的身份证系统」。
- 证书(Certificate):就是数字身份证。里面绑定了「你是谁」和「你的公钥」,然后由一个大家都信任的机构(CA)来签名。
- CA(Certificate Authority):发证机关。它负责审核你的身份,然后用它自己的私钥给你的证书签名。
- RA(Registration Authority):注册审核员。有时候 CA 不直接面对用户,RA 负责做身份审核,然后把审核通过的信息发给 CA 去签发证书。
- 证书吊销列表(CRL):黑名单。如果你的私钥泄露了,CA 会把你的证书编号扔进 CRL 里,告诉大家「这张证作废了」。
核心思想:PKI 解决的是「公钥归属」问题。你信任 CA,CA 担保了设备厂商,设备厂商担保了固件。这就是信任链的传递。
我在做 IoT 设备安全方案时,经常遇到有人问:「我直接把公钥烧死在固件里不就行了?干嘛要搞这么复杂?」嗯,如果你只有一台设备,自己跟自己玩,那确实可以。但一旦你要做 OTA 升级,要管理成千上万台设备,要应对密钥泄露后的紧急吊销……没有 PKI,你连个撤回的机制都没有。
证书链——信任的传递
你想想看,如果全世界所有设备都只信任一个根 CA,那这个根 CA 的私钥一旦泄露,整个体系就崩了。所以实际用的是证书链。
典型的链有三层:
- 根 CA(Root CA):自签名证书。它是信任的起点,通常离线保存,锁在保险柜里。
- 中间 CA(Intermediate CA):由根 CA 签发。它负责具体的签发工作。这样即使中间 CA 出问题,根 CA 还在,可以吊销它并重新签发。
- 终端实体证书(End-Entity Certificate):由中间 CA 签发。这就是你最终烧录到设备里,或者用来给固件签名的那个证书。
验证的时候,设备会从终端证书开始,逐级向上找签发者,直到找到设备本地信任的根证书。只要这条链没断,信任就成立。
我的经验:在固件签名场景里,我建议把根证书的公钥直接编译到 BootROM 里。因为 BootROM 是只读的,物理上没法篡改。中间 CA 证书可以放在固件更新包里,这样即使中间 CA 需要轮换,也不用改硬件。
我曾经在一个客户项目里遇到过一个问题:他们把所有设备的根证书都存到了文件系统里。结果一次攻击者拿到了文件系统的写入权限,直接替换了根证书,然后用自己的假固件签了名……嗯,从那以后,我再也不敢把根证书放在可写存储区了。
椭圆曲线密码学(ECC)简介
聊完 PKI,咱们得说说 ECC。为什么现在越来越多的固件签名方案从 RSA 转向 ECC?说白了,就是「同样的安全强度,ECC 的密钥更短、计算更快」。
| 安全强度(位) | RSA 密钥长度 | ECC 密钥长度 | 性能对比 |
|---|---|---|---|
| 80 | 1024 位 | 160 位 | ECC 快 3-5 倍 |
| 128 | 3072 位 | 256 位 | ECC 快 5-10 倍 |
| 256 | 15360 位 | 512 位 | ECC 优势更明显 |
你看,要达到 128 位的安全强度,RSA 需要 3072 位的密钥,而 ECC 只要 256 位。对于嵌入式设备来说,存储空间和计算资源都是稀缺的,ECC 的优势就体现出来了。
ECC 的数学基础是椭圆曲线上的离散对数问题。具体公式我就不推了,你只需要知道:给定椭圆曲线上的一个点 G(基点)和一个大整数 k(私钥),计算 k*G(公钥)很容易;但反过来,给你 G 和 k*G,让你求 k,非常难。这就是 ECC 的安全基石。
注意:ECC 虽然好,但曲线选择很重要。不要自己发明曲线,直接用标准曲线,比如 NIST P-256(secp256r1)或者 Curve25519。我在早期一个项目里用过一条非标准曲线,结果被安全团队审计出参数有后门风险……后来全部换成了标准曲线。
ECC 在固件签名中的应用
实际签名流程跟 RSA 类似,但算法不同:
- 签名方:用私钥对固件哈希值做 ECDSA 签名,生成一个签名值(r, s)。
- 验证方:用公钥和签名值(r, s)做 ECDSA 验证,确认签名是否匹配。
ECDSA 的签名长度是固定的。对于 P-256 曲线,签名是 64 字节(r 和 s 各 32 字节)。相比 RSA-2048 的 256 字节签名,小了四分之三。这在传输和存储上都是实打实的节省。
// 伪代码示例:ECDSA 签名与验证
// 签名
private_key = 0x... // 32 字节私钥
firmware_hash = SHA256(firmware_binary)
signature = ECDSA_sign(private_key, firmware_hash) // 输出 64 字节
// 验证
public_key = 0x... // 64 字节公钥(未压缩)
is_valid = ECDSA_verify(public_key, firmware_hash, signature)
if is_valid:
print("固件签名验证通过")
else:
print("固件被篡改或签名无效")
这里有个细节:ECDSA 签名过程中需要一个随机数 k。这个 k 必须每次都不一样,而且必须是真正的随机数。如果 k 重复了,私钥就能被算出来。索尼的 PS3 就栽在这个坑上——他们用了固定的 k 值,结果黑客直接算出了索尼的私钥。
避坑指南:实现 ECDSA 签名时,一定要用硬件真随机数发生器(TRNG)来生成 k。不要用伪随机数,更不要用固定的 k。我曾经在代码审查时发现有人用时间戳当 k……这跟把私钥贴在大门上没区别。
PKI + ECC 的典型固件签名架构
最后,我把 PKI 和 ECC 串起来,画一张图,展示一个典型的固件签名信任链。
你看这个结构。设备 BootROM 里只存了根 CA 的公钥。固件更新包里携带了签名证书、中间 CA 证书和 ECDSA 签名。设备启动时,先验证证书链是否合法,再用签名证书的公钥验证固件签名。只要任何一环对不上,固件就不予执行。
嗯,这套架构我用了很多年,基本没出过问题。唯一一次翻车是有人把中间 CA 证书的过期时间设错了,导致大批设备在证书过期后无法升级……从那以后,我每次签发证书都会反复检查有效期。