4. 固件完整性度量:哈希链构建、SM3哈希与SM2签名的结合、度量根(RTM)设计
好,咱们进入第四章。这一章讲的是固件完整性度量,说白了就是怎么证明你的固件没被人动过手脚。我做了这么多年嵌入式安全,见过太多因为没做完整性校验而被攻破的案例。有一次,一个客户的产品在野外被刷了恶意固件,整批设备变成了僵尸网络的一部分。原因?就是他们只做了签名校验,没做链式度量。嗯,咱们今天就把这个坑填上。
4.1 为什么需要哈希链?
你想想看,一个固件通常不是一整块二进制。它包含Bootloader、OS Kernel、文件系统、应用分区等等。如果只给整个固件打一个签名,那启动时就得把整个固件加载完才能验签。这太慢了,而且不安全——万一中间某个阶段被替换了,你根本不知道。
哈希链的思路是:逐级度量,逐级验证。每一级在启动下一级之前,先计算下一级的哈希值,然后用自己的私钥对这个哈希签名。下一级启动时,先验签,再计算自己的哈希比对。这样环环相扣,任何一级被篡改,链条就断了。
核心原则:信任不能传递,只能度量。每一级只信任上一级度量过的内容。
4.2 SM3哈希与SM2签名的结合
国密标准里,SM3负责算哈希,SM2负责签名和验签。这两者怎么配合?我习惯用一个简单的流程:
- 度量阶段:当前级(比如Bootloader)读取下一级(比如OS Kernel)的二进制镜像,用SM3算出256位的哈希值H。
- 签名阶段:当前级用自己的SM2私钥对H签名,生成签名值S。把S和H一起存到安全存储区,或者直接附加在镜像后面。
- 验证阶段:下一级启动时,先读取签名值S,用上一级的SM2公钥验签。验签通过后,再自己算一遍SM3哈希,比对是否等于H。
这里有个细节:签名的是哈希,不是整个镜像。SM2签名算法对输入长度有限制,而且签名整个镜像效率太低。哈希一下,再签名,这是标准做法。
我的习惯:在签名时,我会把哈希值H和当前级的版本号、时间戳一起打包签名。这样能防止重放攻击——有人拿旧版本的合法固件来替换新版本。
4.2.1 代码示例:SM3哈希计算与SM2签名
下面是一个简化的C代码片段,展示如何在Bootloader里计算下一级镜像的SM3哈希,并用SM2签名。实际项目中,SM2的密钥对需要从HSM或安全元件中读取,这里为了演示直接传参。
#include "sm3.h"
#include "sm2.h"
// 计算镜像的SM3哈希
int compute_image_hash(uint8_t *image, uint32_t image_len, uint8_t hash[32]) {
sm3_context ctx;
sm3_init(&ctx);
sm3_update(&ctx, image, image_len);
sm3_final(&ctx, hash);
return 0;
}
// 用SM2私钥签名哈希
int sign_hash(uint8_t *hash, uint32_t hash_len,
uint8_t *private_key, uint32_t key_len,
uint8_t signature[64]) {
sm2_signature sig;
sm2_sign(private_key, hash, hash_len, &sig);
// 将R,S拼接成64字节签名
memcpy(signature, sig.r, 32);
memcpy(signature + 32, sig.s, 32);
return 0;
}
// 验证签名
int verify_signature(uint8_t *hash, uint32_t hash_len,
uint8_t *public_key, uint32_t key_len,
uint8_t signature[64]) {
sm2_signature sig;
memcpy(sig.r, signature, 32);
memcpy(sig.s, signature + 32, 32);
return sm2_verify(public_key, hash, hash_len, &sig);
}
注意:SM2签名验签的输入哈希长度必须是32字节(256位)。SM3的输出正好是32字节,完美匹配。如果你用其他哈希算法,记得做截断或填充。
4.3 度量根(RTM)设计
度量根,Root of Trust for Measurement,是整个信任链的起点。它必须是不可篡改的。我见过一些设计把RTM放在BootROM里,这是最安全的做法——BootROM是只读的,物理上改不了。
RTM的职责很简单:
- 上电后第一个执行。
- 度量下一级(通常是Bootloader)的完整性。
- 把度量结果存到平台配置寄存器(PCR)里。
- 如果度量失败,直接停机或进入恢复模式。
为什么会强调PCR?因为PCR的值只能扩展,不能重置(除非硬件复位)。这样就能保证整个度量链的完整性——你没法跳过某一级去伪造度量结果。
4.3.1 RTM的两种实现方式
| 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 硬件RTM(BootROM) | 不可篡改,安全性最高 | 灵活性差,升级困难 | 高安全等级设备(金融、军工) |
| 软件RTM(安全启动代码) | 可升级,灵活性好 | 依赖硬件保护(如OTP) | 消费类IoT设备 |
我个人更倾向于硬件RTM。虽然升级麻烦,但安全收益是实打实的。有一次,一个客户坚持用软件RTM,结果攻击者通过电压故障注入跳过了RTM的度量代码,直接加载了恶意Bootloader。嗯,从那以后,我再也不敢在关键设备上推荐纯软件RTM了。
4.4 完整的度量链流程
咱们把整个流程串起来,看看一个完整的度量链长什么样。下面这张SVG图展示了从RTM到应用层的四级度量链。
从这张图你能看到,每一级只度量下一级,信任链是单向的。RTM是起点,它度量Bootloader;Bootloader度量OS Kernel;OS Kernel度量文件系统。任何一级度量失败,整个启动过程就终止。
4.5 避坑指南
我在项目里踩过不少坑,挑几个典型的说说:
- 哈希链断裂:有一次,Bootloader升级后忘了更新签名,结果下一级验签失败,设备变砖。后来我加了个机制:如果验签失败,回退到上一个已知良好的版本。
- PCR值被篡改:软件实现的PCR容易被攻击。我建议用硬件TPM或安全元件来存储PCR值,至少也要用OTP(一次性可编程)存储器。
- 度量顺序错误:有些设计先加载镜像再度量,这等于先执行后检查,毫无意义。正确的顺序是:先度量,再加载,最后执行。
曾经有一次:我在一个项目里发现,攻击者通过DMA直接读取了Flash中的固件镜像,绕过了度量链。解决方案是在RTM阶段就把DMA控制器锁住,直到度量完成才释放。这个坑让我意识到,度量链不仅要防篡改,还要防旁路。
4.6 小结
固件完整性度量不是可选项,而是必选项。哈希链保证了每一级都经过验证,SM3+SM2的组合提供了国密标准的密码学保障,RTM则是整个信任链的基石。设计时记住一句话:信任不能传递,只能度量。下一级是否可信,取决于上一级是否正确地度量了它。
嗯,这一章就到这里。内容不少,但都是实战中必须掌握的。如果你在实现中遇到问题,欢迎随时交流。