4、BootROM校验机制:CRC校验原理、SHA哈希校验、RSA签名验证
BootROM 的校验,说白了就是回答一个问题:我怎么知道这段代码是完整的、没被篡改的?
我早年做一款工业控制器时,遇到过启动到一半就死机的情况。查了三天,最后发现是 Flash 里某个字节因为老化变成了 0xFF。从那以后,我对校验机制就特别上心。
BootROM 里常用的校验手段有三种:CRC、SHA、RSA。它们各有各的用处,也各有各的坑。咱们一个一个说。
4.1 CRC校验:快速检错,但不防篡改
CRC(循环冗余校验)是最基础的。它不加密,也不签名,就是算一个固定长度的校验值。
它的核心思路是:把整个镜像当成一个巨大的二进制数,除以一个约定的多项式,余数就是 CRC 值。
CRC 的特点:
- 计算速度快,硬件有专用指令(比如 CRC32)
- 能检测出传输或存储中的随机错误
- 不能防止恶意篡改——攻击者改了数据,可以重新算一个 CRC 填回去
我在项目中常用的 CRC 参数如下:
| 算法 | 多项式 | 初始值 | 输出异或 | 常见场景 |
|---|---|---|---|---|
| CRC-32 | 0x04C11DB7 | 0xFFFFFFFF | 0xFFFFFFFF | 以太网、ZIP、BootROM 头部 |
| CRC-16-IBM | 0x8005 | 0x0000 | 0x0000 | Modbus、简单固件段 |
| CRC-8-ATM | 0x07 | 0x00 | 0x00 | 小数据块、1-Wire |
代码实现也不复杂。这里给一个查表法的 CRC-32 核心片段:
uint32_t crc32_table[256];
void crc32_init_table(void) {
for (uint32_t i = 0; i < 256; i++) {
uint32_t crc = i;
for (int j = 0; j < 8; j++) {
if (crc & 1)
crc = (crc >> 1) ^ 0xEDB88320;
else
crc >>= 1;
}
crc32_table[i] = crc;
}
}
uint32_t crc32_calc(const uint8_t *data, uint32_t len) {
uint32_t crc = 0xFFFFFFFF;
for (uint32_t i = 0; i < len; i++) {
uint8_t index = (crc ^ data[i]) & 0xFF;
crc = (crc >> 8) ^ crc32_table[index];
}
return crc ^ 0xFFFFFFFF;
}
我的习惯:CRC 只用来做快速完整性检查,比如判断镜像是否传输完整。但别把它当安全手段。我曾经见过有人把 CRC 当签名用,结果被轻松绕过了。
4.2 SHA哈希校验:抗碰撞,但依然不防篡改
SHA(安全哈希算法)比 CRC 强在哪儿?强在单向性和抗碰撞性。
你从哈希值推不出原始数据。你也很难找到两个不同的数据,算出同一个哈希值。这就是所谓的抗碰撞。
BootROM 里常用的 SHA 版本:
| 算法 | 输出长度 | 安全性 | BootROM 适用性 |
|---|---|---|---|
| SHA-1 | 160 bit | 已不推荐(有碰撞攻击) | 老旧芯片还在用 |
| SHA-256 | 256 bit | 目前安全 | 主流选择 |
| SHA-512 | 512 bit | 更安全 | 资源充足的场景 |
SHA 的计算比 CRC 慢不少。在 BootROM 里,通常只对关键区域做 SHA 校验,而不是全镜像。
注意:SHA 本身不防篡改。攻击者改了镜像,重新算个 SHA 填回去,校验照样过。SHA 必须配合签名机制才有安全意义。
我见过一个案例:某 IoT 设备用 SHA-256 做固件校验,但哈希值明文存在 Flash 里。结果被逆向后,攻击者直接替换了哈希值和固件,设备就被"合法"刷入了恶意代码。
4.3 RSA签名验证:真正的安全防线
RSA 是非对称加密算法。它用私钥签名,用公钥验证。BootROM 里只放公钥,私钥留在开发者的安全环境中。
流程是这样的:
- 开发者用私钥对固件哈希值签名,生成签名数据
- 签名数据和固件一起烧录或发布
- BootROM 启动时,用内置公钥解密签名,得到哈希值 A
- BootROM 自己算一遍固件哈希值 B
- 如果 A == B,固件可信;否则拒绝启动
RSA 的密钥长度很关键:
| 密钥长度 | 签名长度 | 安全等级 | BootROM 开销 |
|---|---|---|---|
| 1024 bit | 128 字节 | 已不推荐 | 较低 |
| 2048 bit | 256 字节 | 当前安全 | 中等 |
| 4096 bit | 512 字节 | 更高 | 较高(启动慢) |
核心要点:RSA 验证的是签名,不是数据本身。签名验证通过,说明数据确实来自持有私钥的合法方,且未被篡改。
我曾经踩过一个坑:公钥存储在可写的 Flash 区域。攻击者替换了公钥,然后用自己生成的私钥签名恶意固件,BootROM 照样能通过验证。所以,公钥必须放在一次性写入的 OTP 或 eFuse 里。
4.4 三种校验的对比与选择
我把它们放在一起对比,你就清楚了:
| 特性 | CRC | SHA | RSA |
|---|---|---|---|
| 检测随机错误 | ✅ 强 | ✅ 强 | ✅ 强 |
| 防恶意篡改 | ❌ 不行 | ❌ 不行 | ✅ 可以 |
| 计算速度 | 极快 | 较快 | 慢 |
| 密钥管理 | 无 | 无 | 需要保护私钥 |
| 典型用途 | 传输校验 | 完整性校验 | 安全启动 |
实际项目中,我通常这样组合:
- CRC:检查镜像头部和分段传输
- SHA:配合 RSA 做哈希摘要
- RSA:最终的安全签名验证
4.5 知识体系总览
下面这张图,把三种校验的关系和流程画清楚了:
4.6 实战建议
最后,给你几个我在项目中沉淀下来的建议:
- 别把 CRC 当安全手段——它只防误码,不防攻击
- SHA 一定要配合签名——单独用 SHA 等于没锁门
- RSA 公钥要锁死——放在 OTP 或 eFuse 里,一次写入,永不更改
- 签名验证失败要记录——我习惯在 BootROM 里留一个错误日志区,方便调试
我的经验:如果你在做安全启动,建议用 SHA-256 + RSA-2048 的组合。性能和安全之间,这个平衡点最合适。我曾经试过 RSA-4096,启动时间多了将近 2 秒,用户受不了。
嗯,校验机制就聊到这儿。下一层,咱们要深入 BootROM 的启动流程了——看看这些校验到底在哪个阶段被调用,顺序是怎样的。