2、固件完整性基础:完整性定义、哈希算法原理(SHA-256)、数字签名基础
聊固件安全,第一个绕不开的概念就是「完整性」。
很多人把完整性和机密性搞混。机密性是「不让别人看」,完整性是「不让别人改」——哪怕改了你也得知道。我见过不少项目,加密做得滴水不漏,但固件被篡改了却毫无察觉。嗯,这其实更危险。
2.1 完整性的定义
完整性,说白了就是:数据从产生到使用,中间没有被篡改过。
在固件场景下,完整性要保证三件事:
- 源可信:固件确实来自你信任的发布方
- 途未改:固件在传输、存储过程中没被插入恶意代码
- 可验证:设备启动时能自己检查上述两点
核心观点:完整性 ≠ 安全性。完整性只是安全的基础前提。没有完整性,加密、认证都是空中楼阁。
我早期做过一个 IoT 项目,OTA 升级包只做了 AES 加密,没做完整性校验。结果黑客虽然解不开密文,但通过翻转某些比特位,让设备解压后执行了错误逻辑——整批设备变砖。那次之后,我所有项目都把完整性校验放在加密前面。
2.2 哈希算法原理
完整性校验最常用的工具就是哈希算法(Hash)。
哈希函数能把任意长度的数据,映射成一个固定长度的摘要(digest)。这个摘要就像数据的「指纹」——内容变一点点,指纹就完全不一样。
2.2.1 哈希算法的核心性质
| 性质 | 含义 | 为什么重要 |
|---|---|---|
| 确定性 | 同一输入,永远得到同一输出 | 验证时才能比对 |
| 单向性 | 从摘要反推原始数据,计算上不可行 | 摘要泄露不会暴露固件内容 |
| 抗碰撞性 | 找不到两个不同输入产生相同摘要 | 防止伪造固件通过校验 |
| 雪崩效应 | 输入改1比特,输出约一半比特翻转 | 微小篡改能被检测 |
我的习惯:选哈希算法时,优先看「抗碰撞性」是否已被攻破。MD5 和 SHA-1 在理论上已被证明可构造碰撞,坚决不用。
2.2.2 SHA-256 原理
SHA-256 是 SHA-2 家族的一员,输出 256 位(32 字节)摘要。目前仍是工业界最广泛使用的完整性哈希算法。
它的处理流程大致分三步:
- 填充:在消息末尾补位,使长度满足 512 位的整数倍
- 分块:将填充后的消息分成 512 位(64 字节)的块
- 压缩:每个块经过 64 轮迭代压缩,更新 8 个 32 位工作变量
最终这 8 个变量拼接起来,就是 256 位的摘要。
你想想看,64 轮迭代意味着什么?哪怕输入只差一个比特,经过这么多轮扩散,输出早就面目全非了。这就是雪崩效应的工程实现。
我曾经踩过的坑:在某个资源受限的 MCU 上,我直接用软件实现了完整的 SHA-256。结果每次校验耗时 3 秒,用户按电源键都等不及。后来改用硬件哈希加速器,耗时降到 50 毫秒。所以——能用硬件就别用软件,尤其是启动路径上的校验。
2.2.3 代码示例:计算固件 SHA-256 摘要
下面是一个使用 OpenSSL 库计算固件文件 SHA-256 的 C 代码片段。实际嵌入式环境中,你会用硬件驱动或 mbedTLS 的替代接口。
#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>
void compute_firmware_sha256(const char *filepath) {
FILE *fp = fopen(filepath, "rb");
if (!fp) {
perror("fopen");
return;
}
SHA256_CTX ctx;
SHA256_Init(&ctx);
unsigned char buffer[4096];
size_t bytes;
while ((bytes = fread(buffer, 1, sizeof(buffer), fp)) != 0) {
SHA256_Update(&ctx, buffer, bytes);
}
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_Final(hash, &ctx);
printf("SHA-256: ");
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
printf("%02x", hash[i]);
}
printf("\n");
fclose(fp);
}
注意:实际启动校验中,你通常不会在设备上重新计算整个固件的哈希——太慢了。更常见的做法是只校验关键区域(如引导加载程序、签名表),或者使用「分块哈希 + 哈希树」的方式。
2.3 数字签名基础
哈希能检测篡改,但解决不了一个问题:你怎么知道哈希值本身是可信的?
攻击者完全可以替换固件的同时,也替换掉你存储的哈希值。这时候就需要数字签名出场了。
2.3.1 数字签名的工作流程
数字签名基于非对称加密。简单说就是:
- 签名方(固件发布者)用私钥对固件的哈希值加密,生成签名
- 验证方(设备)用公钥解密签名,得到原始哈希值
- 设备再自己计算固件的哈希值,两者比对
如果一致,说明固件没被改,而且确实来自持有私钥的发布者。
关键点:数字签名同时提供了完整性和源认证。哈希只解决前者,签名把两者都解决了。
2.3.2 常见签名算法对比
| 算法 | 密钥长度 | 签名大小 | 计算开销 | 安全性 |
|---|---|---|---|---|
| RSA-2048 | 2048 位 | 256 字节 | 验证较快,签名慢 | 高(广泛使用) |
| ECDSA P-256 | 256 位 | 64 字节 | 验证快,签名快 | 高(推荐) |
| Ed25519 | 256 位 | 64 字节 | 极快 | 极高(现代选择) |
我个人偏好 ECDSA P-256 或 Ed25519。RSA 虽然成熟,但签名太大,在存储受限的 MCU 上不太友好。ECDSA 的 64 字节签名,放在启动镜像头部刚刚好。
2.3.3 一个常见的误解
有人觉得「我用了签名,固件就是安全的」。不对。签名只保证固件没被篡改且来源可信,但它不加密固件内容。如果你需要保密,还得在签名之上做加密。
我的建议:启动校验链路中,签名是必须的,加密是可选的。先把签名做对,再考虑加密。很多安全标准(如 NIST SP 800-193)也只要求启动时做完整性验证,不强制加密。
2.4 本章知识体系图
下面这张图展示了完整性、哈希、签名三者之间的关系,以及它们在固件启动校验中的位置。
2.5 小结
这一章我们拆解了三个层层递进的概念:
- 完整性是目标——确保固件没被改
- 哈希算法是工具——SHA-256 是目前最稳妥的选择
- 数字签名是保障——在哈希之上加了源认证,防止伪造