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 里只放公钥,私钥留在开发者的安全环境中。

流程是这样的:

  1. 开发者用私钥对固件哈希值签名,生成签名数据
  2. 签名数据和固件一起烧录或发布
  3. BootROM 启动时,用内置公钥解密签名,得到哈希值 A
  4. BootROM 自己算一遍固件哈希值 B
  5. 如果 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 知识体系总览

下面这张图,把三种校验的关系和流程画清楚了:

BootROM 校验机制总览 固件镜像 CRC 校验 快速检错,不防篡改 SHA 哈希校验 抗碰撞,仍需配合签名 RSA 签名验证 真正的安全防线 启动 / 拒绝启动 实际项目中:CRC 用于传输校验,SHA 做摘要,RSA 做最终签名验证 公钥必须存储在 OTP/eFuse 中,防止被替换

4.6 实战建议

最后,给你几个我在项目中沉淀下来的建议:

  • 别把 CRC 当安全手段——它只防误码,不防攻击
  • SHA 一定要配合签名——单独用 SHA 等于没锁门
  • RSA 公钥要锁死——放在 OTP 或 eFuse 里,一次写入,永不更改
  • 签名验证失败要记录——我习惯在 BootROM 里留一个错误日志区,方便调试

我的经验:如果你在做安全启动,建议用 SHA-256 + RSA-2048 的组合。性能和安全之间,这个平衡点最合适。我曾经试过 RSA-4096,启动时间多了将近 2 秒,用户受不了。

嗯,校验机制就聊到这儿。下一层,咱们要深入 BootROM 的启动流程了——看看这些校验到底在哪个阶段被调用,顺序是怎样的。