数据处理 -- CRC32校验算法整理

该文章已生成可运行项目,

CRC(循环冗余校验)技术文档

面向:协议/固件工程师、FPGA/ASIC 工程师、驱动开发、逆向与安全分析
目标:从数学定义 → 参数模型 → 常见标准 → 正确实现方式 → 并行/反向/工程坑点


1. CRC 的本质与数学模型

CRC(Cyclic Redundancy Check)不是“简单校验和”,而是定义在有限域 GF(2) 上的线性分组码

1.1 比特串与多项式

在 GF(2) 中,每一比特是一个系数(0 或 1),比特串可映射为一元多项式:

  • 比特串从最高位到最低位,对应从最高次幂到 0 次幂。
  • 示例:比特串 1101011011(共 10 位),从左到右为 bit_9…bit_0:

为 1 的比特在:9, 8, 6, 4, 3, 1, 0
对应多项式:

x9+x8+x6+x4+x3+x+1 x^9 + x^8 + x^6 + x^4 + x^3 + x + 1 x9+x8+x6+x4+x3+x+1

1.2 CRC 编码的标准流程(概念版)

给定:

  • 生成多项式 (G(x)),阶为 (n)(CRC 宽度 = n)
  • 原始消息多项式 (M(x))

编码过程(最经典定义):

  1. 将消息左移 n 位:M(x)⋅xnM(x) \cdot x^nM(x)xn
  2. 计算模 2 多项式除法的余数:
    R(x)=(M(x)xn) mod G(x) R(x) = (M(x)x^n) \bmod G(x) R(x)=(M(x)xn)modG(x)
  3. 发送多项式:
    T(x)=M(x)xn+R(x) T(x) = M(x)x^n + R(x) T(x)=M(x)xn+R(x)
    满足 T(x) mod G(x)=0T(x) \bmod G(x) = 0T(x)modG(x)=0

工程实现里常通过寄存器迭代 + 一堆约定参数来实现这一过程。


2. CRC 参数模型:工程中要记住的是这 6 个

任何一个「CRC 算法」都应由以下参数唯一描述(CRC model):

  1. Width:CRC 位宽,如 4/8/16/32/64。
  2. Poly:生成多项式(除最高位隐含的 xWidthx^WidthxWidth 外的部分),用二进制/十六进制表示系数。
  3. Init:初始寄存器值。
  4. RefIn:输入每个字节时是否按位反转(LSB-first)。
  5. RefOut:输出 CRC 值前是否按位反转。
  6. XorOut:最终 CRC 输出前额外异或的常数。

同一个 Poly,不同 Init/RefIn/RefOut/XorOut → 完全不同的 CRC 变种。
90% 的工程 bug 都是因为这几个没对齐协议。


3. CRC-32 详解

3.1 标准 CRC-32(Ethernet / ZIP 等)

常说的“CRC32”通常指 CRC-32/ADCCP(或称 CRC-32/IEEE 802.3),参数:

  • Width:32

  • Poly:0x04C11DB7
    对应多项式:

    x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1x^{32} + x^{26} + x^{23} + x^{22} + x^{16} + x^{12} + x^{11} + x^{10} + x^8 + x^7+x^5 + x^4 + x^2 + x + 1x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1

  • Init:0xFFFFFFFF

  • RefIn:True

  • RefOut:True

  • XorOut:0xFFFFFFFF

  • 测试向量:“123456789” → 0xCBF43926

在实现层面,当采用 LSB-first + 右移实现时,使用的是 0x04C11DB7 的反射形式:0xEDB88320,这是 Poly 在实现中的另一种写法,不是“换了 CRC 算法”,只是位序换了。

3.2 正确的 bit-wise 实现示例(标准 CRC-32)

def crc32_eth(data: bytes) -> int:
    # 标准 CRC-32 (Ethernet/ZIP) 实现:RefIn=RefOut=True
    poly = 0xEDB88320      # 0x04C11DB7 的反射形式
    crc = 0xFFFFFFFF

    for b in data:
        crc ^= b
        for _ in range(8):
            if crc & 1:
                crc = (crc >> 1) ^ poly
            else:
                crc >>= 1

    return crc ^ 0xFFFFFFFF

该实现对 "123456789" 输出 0xCBF43926,可与标准验证。


4. 常用 CRC 多项式与应用

4.1 CRC-8(示例)

  • Poly:0x07(x8+x2+x+1)(x^8 + x^2 + x + 1)(x8+x2+x+1)
  • 常见参数:Init=0x00, RefIn=False, RefOut=False, XorOut=0x00
  • 应用:简单总线、小数据包。

4.2 CRC-16 家族

1)CRC-16/IBM

  • Poly:0x8005(反射形式实现常写 0xA001)
  • 常见参数(CRC-16/IBM):Init=0x0000, RefIn=True, RefOut=True, XorOut=0x0000
  • 早期协议使用,现代更常见的是基于它的变种。

2)CRC-16/Modbus

  • 属于 CRC-16-IBM 家族的具体变种:
  • Poly:0x8005(实现多用 0xA001)
  • Init=0xFFFF, RefIn=True, RefOut=True, XorOut=0x0000
  • 应用:Modbus RTU/ASCII
  • Modbus 用的是带特定 Init/Ref 的变体。

3)CRC-16-CCITT 系列

基础多项式:

  • Poly:0x1021 (x16+x12+x5+1)(x^{16}+x^{12}+x^5+1)(x16+x12+x5+1)

常见变体:

  • CRC-16/CCITT-FALSE:Init=0xFFFF, RefIn=False, RefOut=False, XorOut=0x0000

  • CRC-16/X25(HDLC、PPP):Init=0xFFFF, RefIn=True, RefOut=True, XorOut=0xFFFF

    • 实现里常看到 Poly=0x8408,这其实是 0x1021 的比特反射形式,用于 LSB-first 右移实现。

HDLC/PPP 修正:

  • 使用的是 CRC-16/IBM 家族?不是。是 CRC-16/CCITT 多项式 0x1021 的反射参数变种(X25)

4.3 CRC-32 常见变种提示

  • CRC-32/ADCCP(Ethernet/ZIP)——最常用。
  • CRC-32/BZIP2 —— RefIn=RefOut=False。
  • CRC-32C —— Poly=0x1EDC6F41(Castagnoli)。
    工程上遇到“CRC32”,一定要看协议给的完整参数,不要想当然。

4.4 USB 专用 CRC

  • USB Token:CRC5,多项式 x5+x2+1x^5 + x^2 + 1x5+x2+1(0x05)
  • USB Data:CRC16,多项式 0x8005(与 CRC-16-IBM 家族相关,参数有协议规定)

5. 实现方式与优化

5.1 Bit-wise(按位算法)

核心思想:模拟多项式除法。

MSB-first 典型步骤(不反射):

  1. crc 初值设为 Init。

  2. 每个字节左对齐到 CRC 高位:crc ^= byte << (width-8)

  3. 重复 8 次:

    • 若最高位为 1:crc = (crc << 1) ^ poly
    • 否则:crc <<= 1
  4. 约束在指定位宽:crc &= (1<<width)-1

  5. 最后做 RefOut(如有)、XorOut。

LSB-first 实现同理,只是用右移 + 反射多项式。

5.2 Table-driven(查表法)

将 256 个可能字节的影响预先算成表,加速到「每字节 O(1)」。

  • 单表(Slice-by-1):经典 256 项表。
  • Slice-by-4/8/16:对多字节并行查多个表,大幅提升吞吐量。

5.3 NEON/SIMD 与并行

  • 对大块数据,可使用 SIMD 一次处理多字节,并配合 Slice-by-4/8。
  • 对极高性能场景,可设计 GF(2) 矩阵乘法 + carry-less multiply(如 CRC-32C 在 x86 PCLMULQDQ 指令上的优化)。

6. 硬件实现要点(FPGA/ASIC)

CRC 本质是线性反馈移位寄存器(LFSR)

  1. 串行实现:每时钟 1 bit 输入,反馈按 Poly 连接。

  2. 并行实现:通过展开多项式,给出 “输入 k bit 后寄存器新值 = 线性组合” 的矩阵表达,组合逻辑一次算多比特。

  3. 常见配置:

    • Init 值可配置;
    • 支持 RefIn/RefOut:通过对输入/输出做比特反转;
    • 支持 XorOut;
    • 支持「继续累加」(用于流式数据)。

协议兼容要点:硬件 LFSR 结构 ≠ Poly 文本本身,务必根据协议给的参数推导 LFSR 拓扑。


7. 分块 / 并行 CRC 的正确组合方式

很多人会犯的错:把各块 CRC 简单 XOR —— 这是错误的。

正确原则(简述版):

  • CRC 是线性的:
    CRC(A∣B)\text{CRC}(A | B)CRC(AB) 可以由 CRC(A)\text{CRC}(A)CRC(A)BBB 的数据推导出来。
  • 若你先算出块 A 的 CRC,再算块 B 的 CRC,不可以直接 XOR。
  • 合并时需要将 CRC(A)\text{CRC}(A)CRC(A) 视为对 A 的余数对应项,并对 B 的长度做 x8⋅len(B)x^{8\cdot len(B)}x8len(B) 的多项式乘法,再与 B 的 CRC 线性组合。

工程实践:

  • 通常用预先生成的“CRC combine”函数或矩阵(如 zlib 就有 crc32_combine)。
  • 记住一句:“能并行算,但要用正确的数学合并,不是拍脑袋 XOR。”

8. 反向 CRC、补偿字节与攻击视角

因为 CRC 是线性的:

  1. 给定消息剩余部分和目标 CRC,能构造若干「补偿字节」使整体 CRC 等于期望值(fix-up byte)。
  2. 可以对单比特 / 多比特翻转设计,使 CRC 不变或变为预期值。
  3. 因此 CRC 适合作为错误检测,不适合作为安全校验/防篡改手段

在协议/SW 里:

  • CRC != 安全哈希,不能用来防伪造。

9. 生成多项式选择与工程建议

选型时考虑:

  1. 码长 & 场景

    • 小数据包 / 简单总线:CRC-8 / CRC-16 即可。
    • 网络帧 / 文件 / 存储块:CRC-32 / CRC-32C。
  2. 标准优先

    • 能用协议/行业标准就别造轮子。
    • 同一类设备需兼容,就统一使用公开模型(参数六件套)。
  3. 错误检测能力

    • 多项式决定检测单比特、双比特、奇偶数比特、突发错误的上界。
    • 如 CRC-32C(Castagnoli)对短帧有更好性能与检测特性,在很多现代协议中被采用。
  4. 实现成本

    • 软件中选便于表驱动/指令优化的;
    • 硬件中选反馈 taps 少、逻辑扇出适中的。

10. 按字节处理的示例

这一节专门把「如何按字节处理」讲清,用标准 CRC-32(Ethernet/ZIP)参数做精确示范;

10.1 使用的 CRC 模型(固定死)

后文所有示例均指下述 CRC-32(IEEE 802.3):

  • Width:32
  • Poly:0x04C11DB7(实现用反射多项式 0xEDB88320)
  • Init:0xFFFFFFFF
  • RefIn:True
  • RefOut:True
  • XorOut:0xFFFFFFFF

采用LSB-first + 右移实现,对应伪代码:

crc = 0xFFFFFFFF
for each byte b in data:
    crc ^= b
    repeat 8 times:
        if (crc & 1) == 1:
            crc = (crc >> 1) ^ 0xEDB88320
        else:
            crc >>= 1
        crc &= 0xFFFFFFFF
crc ^= 0xFFFFFFFF

注意:因为 RefIn=True,这里是直接 XOR b,而不是 b << 24;多出来的那种写法通常对应非反射变种。


10.2 单字节 0x31 的逐位推导(精细步骤)

数据字节:0x31(ASCII ‘1’,二进制 0011 0001

  1. 初始值
crc = 0xFFFFFFFF
  1. 与当前字节异或(RefIn=True,低位对齐)
crc = 0xFFFFFFFF ^ 0x31 = 0xFFFFFFCE
  1. 对该字节进行 8 次按位迭代(每次处理当前 crc 的最低位):

记多项式常量:P = 0xEDB88320

下面每一步:

  • 先看 crc & 1(最低位)
  • 若为 1:crc = (crc >> 1) ^ P
  • 若为 0:crc = crc >> 1
  • 然后约束到 32 位。
步骤说明操作新 crc(hex)当前最低位
初值初始异或后-0xFFFFFFCE0
1bit0:最低位=0 → 右移crc >>= 10x7FFFFFE71
2bit1:最低位=1 → 右移 ^ P(0x7FFFFFE7>>1)^P0xD2477CD31
3bit2:最低位=1 → 右移 ^ P(0xD2477CD3>>1)^P0x849B3D491
4bit3:最低位=1 → 右移 ^ P(0x849B3D49>>1)^P0xAFF51D840
5bit4:最低位=0 → 右移0xAFF51D84>>10x57FA8EC20
6bit5:最低位=0 → 右移0x57FA8EC2>>10x2BFD47611
7bit6:最低位=1 → 右移 ^ P(0x2BFD4761>>1)^P0xF84620900
8bit7:最低位=0 → 右移0xF8462090>>10x7C231048-

因此:

处理完首字节 0x31 后的中间 CRC 值为:0x7C231048(尚未做最终 XorOut,也未处理后续字节)。

这一步完整演示了:

  • 为什么看最低位(RefIn=True → 右移);
  • 为什么要按位判断是否异或多项式;
  • 如何从 Init + 一个字节,得到正确中间状态。

10.3 多字节 "123456789" 的完整中间结果

同样用上述标准 CRC-32 算法,依次处理 9 个字节:

数据:b"123456789"
字节序列(十六进制):31 32 33 34 35 36 37 38 39

每处理完一个字节(包含 8 次 bit 循环)后的中间 CRC(未做最终 ^0xFFFFFFFF)如下:

字节序号字符十六进制处理后中间 CRC(hex)
1‘1’0x310x7C231048
2‘2’0x320xB0ACBB32
3‘3’0x330x77B79C2D
4‘4’0x340x641C1F5C
5‘5’0x350x340AC5E3
6‘6’0x360xF68D2C9E
7‘7’0x370xAFFC9660
8‘8’0x380x651F2550
9‘9’0x390x340BC6D9

完成全部字节后,执行最终步骤:

Final CRC = 0x340BC6D9 ^ 0xFFFFFFFF = 0xCBF43926

这就是标准 CRC-32 对 "123456789" 的结果,用来验证你实现是否完全正确。


11. 并行优化的版本

完整可跑的 Python 脚本,里边包含:

  • 标准 CRC-32(Ethernet/ZIP)bit-wise 实现
  • 正确的 Slice-by-4 实现(对齐 zlib BYFOUR 版本)
  • 正确的 crc32_combine(对齐 zlib 官方实现)
  • "123456789" 和分块 "1234" + "56789" 做对比验证
python3 crc32_verify.py

输出应该完全一致。


#!/usr/bin/env python3
import zlib

# 标准 CRC-32 (Ethernet/ZIP) 参数:
# Poly = 0x04C11DB7 (实现使用反射形式 0xEDB88320)
# Init = 0xFFFFFFFF, RefIn = True, RefOut = True, XorOut = 0xFFFFFFFF

POLY_REFLECTED = 0xEDB88320
INIT = 0xFFFFFFFF
XOROUT = 0xFFFFFFFF
GF2_DIM = 32  # CRC32 位宽

# ========== 1. 基准:bit-wise 实现 ==========
def crc32_bitwise(data: bytes) -> int:
    crc = INIT
    for b in data:
        crc ^= b
        for _ in range(8):
            if crc & 1:
                crc = (crc >> 1) ^ POLY_REFLECTED
            else:
                crc >>= 1
            crc &= 0xFFFFFFFF
    return crc ^ XOROUT

# ========== 2. 生成基础查表(Slice-by-1) ==========
def make_table(poly: int):
    """生成标准反射 CRC-32 的 256 项查表(和 zlib 第一张表一致)"""
    tbl = []
    for n in range(256):
        c = n
        for _ in range(8):
            if c & 1:
                c = (c >> 1) ^ poly
            else:
                c >>= 1
        tbl.append(c & 0xFFFFFFFF)
    return tbl

# ========== 3. 生成 Slice-by-4 的 4 张表 ==========
def make_slice4_tables(poly: int):
    """
    按照 zlib BYFOUR 逻辑生成 4 张表:
    T0: 单字节表
    T1/T2/T3: 对应该字节位于 4 字节块中更高字节位置时的影响
    """
    T0 = make_table(poly)
    T1 = [0] * 256
    T2 = [0] * 256
    T3 = [0] * 256

    for n in range(256):
        c = T0[n]
        # k=1
        c = T0[c & 0xFF] ^ (c >> 8)
        T1[n] = c
        # k=2
        c = T0[c & 0xFF] ^ (c >> 8)
        T2[n] = c
        # k=3
        c = T0[c & 0xFF] ^ (c >> 8)
        T3[n] = c

    return T0, T1, T2, T3

T0, T1, T2, T3 = make_slice4_tables(POLY_REFLECTED)

# ========== 4. 正确的 Slice-by-4 实现 ==========
def crc32_slice4(data: bytes) -> int:
    """
    Slice-by-4 优化版本,等价于标准 CRC-32。
    对齐 zlib 中 crc32_little() 的 DOLIT4 宏逻辑:
      c ^= *(uint32_t *)buf;
      c = t3[c & 0xff] ^ t2[(c >> 8) & 0xff] ^
          t1[(c >> 16) & 0xff] ^ t0[c >> 24];
    """
    crc = INIT
    n = len(data)
    i = 0

    # 处理 4 字节块
    while i + 4 <= n:
        d = (
            data[i]
            | (data[i + 1] << 8)
            | (data[i + 2] << 16)
            | (data[i + 3] << 24)
        )
        crc ^= d
        crc = (
            T3[(crc      ) & 0xFF] ^
            T2[(crc >> 8 ) & 0xFF] ^
            T1[(crc >> 16) & 0xFF] ^
            T0[(crc >> 24) & 0xFF]
        )
        i += 4

    # 处理剩余字节(退回 Slice-by-1)
    while i < n:
        crc = T0[(crc ^ data[i]) & 0xFF] ^ (crc >> 8)
        i += 1

    return crc ^ XOROUT

# ========== 5. 多块 CRC 合并:crc32_combine ==========
def gf2_matrix_times(mat, vec):
    """GF(2) 矩阵 * 向量"""
    sum_ = 0
    idx = 0
    while vec:
        if vec & 1:
            sum_ ^= mat[idx]
        vec >>= 1
        idx += 1
    return sum_

def gf2_matrix_square(square, mat):
    """square = mat^2 (GF(2))"""
    for n in range(GF2_DIM):
        square[n] = gf2_matrix_times(mat, mat[n])

def crc32_combine(crc1, crc2, len2):
    """
    等价于 zlib 的 crc32_combine():
    已有:
      crc1 = CRC32(M1)
      crc2 = CRC32(M2)
    返回:
      CRC32(M1 || M2)
    len2 是 M2 的字节长度。
    注意:crc1/crc2 是“最终 CRC”(已做 XOROUT)的值。
    """
    if len2 <= 0:
        return crc1

    # odd/even 矩阵用于构造 x^(len2*8) 作用在 CRC 上的线性变换
    odd = [0] * GF2_DIM
    even = [0] * GF2_DIM

    # one zero bit operator
    odd[0] = POLY_REFLECTED
    row = 1
    for n in range(1, GF2_DIM):
        odd[n] = row
        row <<= 1

    # two zero bits
    gf2_matrix_square(even, odd)
    # four zero bits
    gf2_matrix_square(odd, even)

    # 对 crc1 应用 len2*8 个“0 bit”(即 len2 个零字节)的变换
    while True:
        gf2_matrix_square(even, odd)
        if len2 & 1:
            crc1 = gf2_matrix_times(even, crc1)
        len2 >>= 1
        if len2 == 0:
            break

        gf2_matrix_square(odd, even)
        if len2 & 1:
            crc1 = gf2_matrix_times(odd, crc1)
        len2 >>= 1
        if len2 == 0:
            break

    # 合并
    crc1 ^= crc2
    return crc1

# ========== 6. 验证部分 ==========

if __name__ == "__main__":
    data = b"123456789"
    part1 = b"1234"
    part2 = b"56789"

    # 1) 三种方式对整串 "123456789"
    c_bit = crc32_bitwise(data)
    c_slice4 = crc32_slice4(data)
    c_zlib = zlib.crc32(data) & 0xFFFFFFFF

    print(f"bitwise : 0x{c_bit:08X}")
    print(f"slice-4 : 0x{c_slice4:08X}")
    print(f"zlib    : 0x{c_zlib:08X}")

    # 2) 分块计算 + 合并
    c1 = zlib.crc32(part1) & 0xFFFFFFFF
    c2 = zlib.crc32(part2) & 0xFFFFFFFF
    c_full = zlib.crc32(data) & 0xFFFFFFFF
    c_comb = crc32_combine(c1, c2, len(part2))

    print(f"CRC(part1)     = 0x{c1:08X}")
    print(f"CRC(part2)     = 0x{c2:08X}")
    print(f"CRC(full)      = 0x{c_full:08X}")
    print(f"combine(p1,p2) = 0x{c_comb:08X}")

你在终端应看到:

bitwise : 0xCBF43926
slice-4 : 0xCBF43926
zlib    : 0xCBF43926
CRC(part1)     = 0x9BE3E0A3
CRC(part2)     = 0x131DA070
CRC(full)      = 0xCBF43926
combine(p1,p2) = 0xCBF43926
本文章已经生成可运行项目
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值