校验算法入门:CRC、MD5、SHA 与 Checksum

做嵌入式固件烧录,最怕什么?

怕镜像在传输过程中被“污染”。哪怕只错一个 bit,设备启动时可能直接变砖。我早年调试一款工业控制器时,就遇到过这种问题——串口线长了点,干扰大,烧了十片有八片起不来。后来一查,就是缺了校验这一步。

所以这一章,咱们把校验算法的基础打牢。你搞懂了 CRC、MD5、SHA 和 Checksum 的原理,后面讲串口烧录校验流程时,才能知道每一步在防什么。

校验的本质:冗余与比对

说白了,校验就是在原始数据后面附加一小段“指纹”。接收方拿到数据后,用同样的算法重新算一遍指纹,跟发过来的指纹比对。一致,说明数据没坏;不一致,说明有错误。

这个“指纹”怎么算,就是不同算法的区别。有的算法快但防不住刻意篡改,有的算法慢但几乎不可能碰撞。选哪个,取决于你的场景。

核心原则:校验算法不是越强越好,而是越“够用”越好。串口烧录场景下,我们既要防随机干扰,也要防镜像文件被恶意替换。所以通常会用两层校验——一层快速 CRC,一层强哈希。

校验和(Checksum)——最朴素的做法

Checksum 是所有校验算法的老祖宗。做法很简单:把数据按字节(或字)累加,只保留低 N 位作为校验值。

举个例子,假设你要发送三个字节:0x01, 0x02, 0x03。累加得到 0x06,这就是校验和。接收方收到后也累加一遍,如果结果不是 0x06,说明数据错了。

// 最简单的字节累加校验和
uint8_t checksum(uint8_t *data, uint32_t len) {
    uint8_t sum = 0;
    for (uint32_t i = 0; i < len; i++) {
        sum += data[i];
    }
    return sum;
}

Checksum 的优点是快,几乎不占 CPU。但缺点也很明显:

  • 无法检测字节顺序错误——比如 0x01, 0x02 和 0x02, 0x01 累加结果一样。
  • 无法检测数据位翻转的抵消——一个字节加 1,另一个字节减 1,累加和不变。
  • 容易被刻意伪造——攻击者可以轻易构造出相同校验和的不同数据。

所以,Checksum 只适合用在低安全要求的场景,比如内部通信的简单完整性检查。我在早期做 Bootloader 时,只在调试阶段用 Checksum,正式产品从来不用。

CRC(循环冗余校验)——工业界的标配

CRC 比 Checksum 聪明得多。它不光是累加,而是把数据当作一个巨大的二进制数,用多项式除法求余数。这个余数就是 CRC 值。

为什么 CRC 比 Checksum 可靠?因为多项式除法对数据的每一位都敏感,哪怕只翻转一个 bit,余数也会剧烈变化。而且 CRC 能检测出所有长度不超过其多项式阶数的突发错误。

我的经验:串口烧录中最常用的 CRC 是 CRC-32(多项式 0x04C11DB7)。它速度快,硬件上很多 MCU 都有 CRC 外设,直接写寄存器就能算。我建议你在 Bootloader 里优先用硬件 CRC,省 CPU 还省电。

CRC 的典型计算流程:

  1. 初始化 CRC 寄存器为全 1(或全 0,取决于具体算法)。
  2. 逐字节处理数据,每 bit 与多项式进行异或和移位。
  3. 处理完所有字节后,对结果取反(部分算法需要)。
  4. 得到最终的 CRC 值。
// 软件实现 CRC-32(简化版,实际工程建议用查表法)
uint32_t crc32_soft(uint8_t *data, uint32_t len) {
    uint32_t crc = 0xFFFFFFFF;
    for (uint32_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 1) {
                crc = (crc >> 1) ^ 0xEDB88320;
            } else {
                crc >>= 1;
            }
        }
    }
    return ~crc;
}

注意:CRC 算法有很多变种——CRC-8、CRC-16、CRC-32,甚至 CRC-64。不同变种的多项式、初始值、输出异或值都不一样。烧录时,上位机和 Bootloader 必须使用完全相同的 CRC 参数,否则校验永远过不了。我曾经被这个坑过——上位机用 CRC-32/MPEG2,下位机用 CRC-32/BZIP2,折腾了一下午才发现。

MD5 与 SHA——哈希算法的代表

CRC 虽然强,但它不是密码学安全的。什么意思?就是攻击者可以刻意构造出两个不同数据,但 CRC 值相同。这在安全烧录场景下是致命的——如果有人替换了你的镜像文件,但重新计算了 CRC,Bootloader 根本发现不了。

这时候就需要哈希算法上场了。MD5 和 SHA 系列都是单向哈希函数:从数据到哈希值很容易,但从哈希值反推数据几乎不可能。而且,哪怕数据只改一个 bit,哈希值也会完全变样。

算法 输出长度 安全性 速度 典型用途
MD5 128 bit(16 字节) 已破解,可碰撞 快 非安全场景的完整性校验
SHA-1 160 bit(20 字节) 已破解,不推荐 中等 遗留系统兼容
SHA-256 256 bit(32 字节) 目前安全 较慢 安全烧录、固件签名验证
SHA-512 512 bit(64 字节) 目前安全 慢 高安全等级场景

MD5 虽然已经被学术界证明不安全,但在很多老 Bootloader 里还在用。我个人建议:新项目直接上 SHA-256。MCU 资源够的话,用硬件加速模块算 SHA-256,速度不比软件 CRC 慢多少。

避坑指南:我曾经在一个项目里用 MD5 做镜像校验,结果被客户的安全审计发现了。他们要求必须用 SHA-256,我只好重新改 Bootloader。从那以后,我所有新项目都默认用 SHA-256,省得后面返工。

三种校验算法的对比

为了让你看得更清楚,我把 Checksum、CRC、SHA 放在一起比一下:

特性 Checksum CRC SHA(如 SHA-256)
计算复杂度 极低 低(硬件加速时极快) 高
检测随机错误能力 弱 强(可检测所有 ≤ 阶数突发错误) 极强
抗刻意篡改能力 无 弱 强(目前不可碰撞)
输出长度 1~4 字节 1~8 字节 16~64 字节
典型应用 UART 简单帧校验 串口烧录、Flash 写入校验 固件签名、安全启动

知识体系总览

下面这张图把本章的核心逻辑串起来了。你一看就明白:不同校验算法在串口烧录流程中各自扮演什么角色。

串口烧录校验算法体系 校验目标:保证镜像完整性 Checksum(校验和) CRC(循环冗余校验) SHA(安全哈希算法) 特点 极快,但防不住刻意篡改 适合简单帧校验 特点 硬件加速,检测随机错误强 串口烧录首选 特点 抗碰撞,密码学安全 固件签名验证 输出:1~4 字节 输出:1~8 字节 输出:16~64 字节 实际应用:CRC + SHA 双层校验

你看,从顶层到底层,逻辑很清晰:Checksum 最轻量但最弱,CRC 是工业主力,SHA 是安全担当。实际串口烧录中,我通常用 CRC 做每包数据的快速校验,用 SHA-256 做整个镜像文件的最终完整性验证。两层配合,既保证了速度,又守住了安全底线。

一个小建议:如果你在资源受限的 MCU 上做 Bootloader,CRC 用硬件外设算,SHA-256 用软件库(比如 mbedTLS 的小体积版本)。别自己手写哈希算法,容易出 bug。我见过有人自己实现 SHA-256,结果算出来的哈希值跟 OpenSSL 对不上,查了三天才发现是字节序搞反了。

好了,校验算法的基础就讲到这里。你理解了 Checksum、CRC 和 SHA 的区别,后面讲串口烧录协议时,就能明白为什么要在不同环节用不同的校验方式。记住:没有最好的算法,只有最合适的组合。