校验算法入门: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 的典型计算流程:
- 初始化 CRC 寄存器为全 1(或全 0,取决于具体算法)。
- 逐字节处理数据,每 bit 与多项式进行异或和移位。
- 处理完所有字节后,对结果取反(部分算法需要)。
- 得到最终的 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 是安全担当。实际串口烧录中,我通常用 CRC 做每包数据的快速校验,用 SHA-256 做整个镜像文件的最终完整性验证。两层配合,既保证了速度,又守住了安全底线。
一个小建议:如果你在资源受限的 MCU 上做 Bootloader,CRC 用硬件外设算,SHA-256 用软件库(比如 mbedTLS 的小体积版本)。别自己手写哈希算法,容易出 bug。我见过有人自己实现 SHA-256,结果算出来的哈希值跟 OpenSSL 对不上,查了三天才发现是字节序搞反了。
好了,校验算法的基础就讲到这里。你理解了 Checksum、CRC 和 SHA 的区别,后面讲串口烧录协议时,就能明白为什么要在不同环节用不同的校验方式。记住:没有最好的算法,只有最合适的组合。