CRC — 循环冗余校验,从零讲起
一句话概括:CRC 把你的数据当作一个巨大的二进制数,用一个固定的“生成数”以 XOR 代替减法做除法,并报告余数 — 一个紧凑的指纹,输入哪怕翻转一个比特都会剧烈改变,这正是从 PNG 文件到 Modbus 线路都在用它校验的原因。
CRC 究竟是什么
除法图景。把你的字节排成一个长长的二进制数。选一个除数 — 即多项式,例如 CRC-32 的 0x04C11DB7。执行二进制长除法,其中“减法”就是 XOR 操作:只要运行余数的最高位是 1,就 XOR 进除数,左移,重复。整条消息走完之后,寄存器里剩下的就是 CRC。没有密钥,定义里也没有查表 — 只是有余数的除法。
五个参数。两套系统都可以自称“CRC-32”却互相不一致,因为名字本身什么也没定。完整的配方需要五个值:poly(除数)、init(寄存器的起始值 — 全零会让开头的零字节凭空消失,全一不会)、refin/refout(每个输入字节 — 以及最终寄存器 — 是否按位反转,这是“低位先发”的串行硬件留下的遗产)、xorout(最终 XOR 掩码,让一串结尾零也能改变结果)。本页在每个结果旁打印全部五个值,正是为此。
为什么它能检错。选得好的除数能让“消息的余数”与“同一消息翻转若干比特后的余数”以压倒性概率不同:一个好的 CRC-32 能抓住全部 1 比特和 2 比特错误、全部奇数个比特错误,以及所有不超过 32 个连续损坏比特的突发错误 — 这是保证,不是概率。这一保证针对的是意外损坏,也正是 CRC 成为嘈杂线路与不稳定磁盘首选工具的原因。
CRC 不是什么
不是密码学哈希。它没有秘密可言,也没有单向性:任何知道参数的人都能在几毫秒内追加四个修正字节,伪造出任意想要的 CRC。CRC 检测的是意外,从来不是对手。要核对陌生人发布的下载内容,签名才有意义 — 请改用 哈希生成器(MD5/SHA/BLAKE2)。那一页还解释了为什么 MD5 和 SHA-1 在对抗性场合已退役,而 SHA-256 依然可用。
不是纠错码。CRC 告诉你有东西变了,而不是什么变了。即时修复错误是 Reed–Solomon、Hamming 这类码的工作,它们刻意存储可供解码器还原的冗余结构。
不是压缩,也不是加密。无论输入多大,余数都只有几个字节,而且无法从余数恢复输入 — 但与密码学哈希不同,这仅仅是因为信息被丢弃了,而不是因为求逆困难。
本页的各大家族
CRC-32 四胞胎。本页有四项共用多项式 0x04C11DB7 却给出不同结果:ISO-HDLC(反射,init/xorout 全一)就是 zlib、gzip、PNG、ZIP 所说的“那个”CRC-32;BZIP2 是同一数学的非反射版;MPEG-2 是没有最终 XOR 的 BZIP2;POSIX 从零起步,是 cksum 命令的基础(后者在除法前额外附加文件长度,所以同一文件的 cksum 与其字节的 CRC-32/POSIX 不同)。CRC-32C(Castagnoli,多项式 0x1EDC6F41)是第五个兄弟:为更好的检错性能另选的多项式,也是现代 x86 与 ARM CPU 能用一条指令以内存速度计算的那个。
CRC-64 四重奏。XZ 用 ECMA-182 多项式的反射形式,init 和 xorout 全一;ECMA-182 本身是 DLT-1 磁带标准里零初始化的非反射原版;GO-ISO 是 Go 的 crc64.ISO,一个极小的多项式 0x1B,同样反射;REDIS 的多项式则是为哈希槽分配的良好雪崩性而选。64 位正是 CRC 开始被当作哈希函数(分桶)而非纯粹校验和使用的分水岭。
Adler-32 是局外人:它根本不做除法。它对字节求和(A,从 1 开始),再对这些和求和(B),两者都对 65521 取模,然后把两个 16 位和拼接起来。它在软件中比 CRC-32 快,zlib 用它处理 deflate 流 — 但对短输入(几百字节以下)求和携带的信息太少,这正是 zlib 对小块改回 CRC-32 的原因。
CRC-8 与 CRC-16 活在字节很金贵的地方:CRC-8/CCITT(多项式 0x07)保护 SMBus 与蜂窝射频协议里的单个头部字节;CRC-16/CCITT-FALSE 守卫智能卡数据块;CRC-16/MODBUS 是全世界工厂车间里每条 Modbus RTU 帧的校验和。
各算法的用武之地
| 参数集 | 见于 |
| CRC-32/ISO-HDLC | zlib、gzip、PNG、ZIP、以太网(FCS)、多种归档格式 |
| CRC-32C | iSCSI、ext4、btrfs、ZFS、SSE4.2/ARMv8 硬件 CRC32C 指令 |
| CRC-32/BZIP2 | .bz2 归档流 |
| CRC-32/MPEG-2 | MPEG-2 传输流 |
| CRC-32/POSIX & cksum | POSIX cksum 命令输出(cksum file.txt) |
| CRC-16/MODBUS | Modbus RTU 帧(工业 PLC、仪表、逆变器) |
| CRC-16/CCITT-FALSE | 智能卡、X.25 类链路、部分 GPS 协议 |
| CRC-8/CCITT | SMBus(PEC 字节)、蜂窝射频头部 |
| CRC-64/XZ | .xz 容器完整性校验 |
| CRC-64/ECMA-182 | ECMA-182 / DLT-1 磁带标准 |
| CRC-64/GO-ISO | Go 标准库 crc64.ISO |
| CRC-64/REDIS | Redis 集群哈希槽分配 |
| Adler-32 | zlib 流(deflate)、其他快速校验和场景 |
锚点表 — check(“123456789”)
CRC 目录的惯例是发布 ASCII 字符串 123456789(9 字节)的结果。下表每个值都能在本页复现:在文本框输入 123456789,勾选算法,点击“计算”。
| 参数集 | 宽度 | 校验值(十六进制) |
| CRC-8/CCITT | 8 | 0xf4 |
| CRC-16/CCITT-FALSE | 16 | 0x29b1 |
| CRC-16/MODBUS | 16 | 0x4b37 |
| CRC-32/ISO-HDLC | 32 | 0xcbf43926 |
| CRC-32C | 32 | 0xe3069283 |
| CRC-32/BZIP2 | 32 | 0xfc891918 |
| CRC-32/MPEG-2 | 32 | 0x0376e6e7 |
| CRC-32/POSIX | 32 | 0x765e7680 |
| cksum (POSIX command) | 32 | 0x377a6011 |
| CRC-64/XZ | 64 | 0x995dc9bbdf1939fa |
| CRC-64/ECMA-182 | 64 | 0x6c40df5f0b497347 |
| CRC-64/GO-ISO | 64 | 0xb90956c775a41001 |
| CRC-64/REDIS | 64 | 0xe9c6d914c4b8d9ca |
| Adler-32 | 32 | 0x091e01de |
数据来源:每个值都经独立的按位参考实现交叉验证,并在存在系统库实现的地方与系统库核对(CRC-32 与 Adler-32 对 zlib,其余对照 Greg Cook 的 CRC 目录)。
常见错误
- 没有参数的“CRC-32”。ISO-HDLC、BZIP2、MPEG-2、POSIX 和 CRC-32C 共五个不同值都顶着同一个名字流通。务必把 poly/init/refin/refout/xorout 随结果一起记录 — 本页已经替你印好了。
- 拿文件的 CRC-32 与它的
cksum 输出比较。这是两种算法:cksum 会附加文件长度且用不同的 init。数字不会一致,而两个都“对”。
- 指望 CRC 抵御篡改。伪造匹配的 CRC 是教科书练习(补最后 4 个字节即可)。当对方可能怀有敌意时,请使用带密钥的 MAC(HMAC)或签名。
- 期待雪崩效应。CRC 是线性的:相关的消息给出相关的 CRC。对付噪声没问题,做哈希就不对了 — 需要分桶时,CRC-64 或真正的哈希都比 CRC-32 稳妥。
- 十六进制文本与字节混淆。字符串
"123456789"(9 个 ASCII 字节,31 32 33 34 35 36 37 38 39)和数字 0x123456789 是不同的输入,CRC 也不同。源数据是真正的二进制时,请用“十六进制字节”标签页。
常见问题
为什么我的 CRC-32 与 ZIP 文件里的对不上?ZIP 的 CRC-32 是对未压缩数据算的 ISO-HDLC。确认你哈希的是同一批字节(不是压缩流)、同一模式(文本还是十六进制)、同一参数集 — 每个结果下面印着的参数行就是为了让这种核对一秒钟完成。
CRC-32C 和 CRC-32 哪个更好?就意外检错而言,两者都能抓住全部 32 位以内的突发错误;CRC-32C 在某些错误模式上表现更好,而且是有 CPU 指令的那个 — 千兆速度下几乎免费。CRC-32/ISO-HDLC 能活下来靠的是兼容性:三十年的存档都说这门语言。
CRC-64 比 CRC-32 强吗?它能检测更长的突发(最长 64 位),用于分桶时碰撞少得多,但同样可以被伪造 — 位宽买到的是检错保证,不是安全性。
CRC 能反推出输入吗?不能 — 无穷多个输入共享同一个 CRC。但与密码学哈希不同,攻击者可以构造出任意指定 CRC 的输入,从这个意义上说它根本不单向。