【问题标题】:How is a CRC32 checksum calculated?如何计算 CRC32 校验和?
【发布时间】:2011-02-04 23:26:54
【问题描述】:

也许我只是没有看到它,但 CRC32 看起来要么是不必要的复杂,要么是我在网络上可以找到的任何地方都没有充分解释。

我知道它是消息值的非基于进位的算术除法的余数,除以(生成器)多项式,但它的实际实现让我无法理解。

我读过A Painless Guide To CRC Error Detection Algorithms,我必须说这不是无痛的。它很好地解释了理论,但作者从来没有得到一个简单的“就是这样”。他确实说明了标准 CRC32 算法的参数是什么,但他忽略了清楚地说明你是如何得到它的。

让我印象深刻的是,他说“就是这样”,然后补充说,“哦,顺便说一下,它可以颠倒过来,也可以从不同的初始条件开始”,但并没有给出明确的答案考虑到他刚刚添加的所有更改,计算 CRC32 校验和的最终方法。

  • 对于如何计算 CRC32 是否有更简单的解释?

我尝试用 C 编写表格的形成方式:

for (i = 0; i < 256; i++)
{
    temp = i;

    for (j = 0; j < 8; j++)
    {
        if (temp & 1)
        {
            temp >>= 1;
            temp ^= 0xEDB88320;
        }
        else {temp >>= 1;}
    }
    testcrc[i] = temp;
}

但这似乎生成的值与我在 Internet 上其他地方找到的值不一致。我可以使用我在网上找到的值,但我想了解它们是如何创建的。

在清理这些令人难以置信的令人困惑的数字方面的任何帮助都将非常感激。

【问题讨论】:

  • 您生成 CRC32 表的代码似乎是正确的。 0xEDB88320 的 lsbit-first (reversed) CRC32 多项式也可以写为 msbit-first (normal) 0x04C11DB7。您在其他地方找到的表格值是否使用相同的 CRC 多项式生成?
  • @jschmier 嗨,我觉得我比这个提问的人落后了一步? *.com/questions/62168128/…
  • 如果其他人有兴趣阅读上面链接的“CRC 错误检测算法的无痛指南”,该原始 URL 已被破坏,但谷歌很容易找到几个副本,包括这个:zlib.net/crc_v3.txt跨度>

标签: c checksum crc32


【解决方案1】:

CRC 非常简单;您将多项式表示为位和数据,并将多项式划分为数据(或者您将数据表示为多项式并执行相同的操作)。介于 0 和多项式之间的余数是 CRC。您的代码有点难以理解,部分原因是它不完整:没有声明 temp 和 testcrc,因此不清楚索引的内容以及算法运行的数据量。

理解 CRC 的方法是尝试使用带有短多项式的一小段数据(16 位左右)来计算一些 CRC,也许是 4 位。如果您以这种方式练习,您将真正了解如何编写代码。

如果您经常这样做,那么在软件中计算 CRC 会很慢。硬件计算效率更高,并且只需要几个门。

【讨论】:

  • 对于 CRC32 或 CRC32b ,我们是否得到两个不同字符串的哈希冲突含义我们得到相同的 CRC
  • 嗨,我有点困惑你所说的“将多项式除以数据”是什么意思? *.com/questions/62168128/… 多项式中的 X 代表什么?我是否使用块中的其他字节?
【解决方案2】:

CRC32 的多项式是:

x32 + x26 + x23 + x22 + x16 sup> + x12 + x11 + x10 + x8 + x7 sup> + x5 + x4 + x2 + x + 1

或者十六进制和二进制:

0x 01 04 C1 1D B7
1 0000 0100 1100 0001 0001 1101 1011 0111

最高项 (x32) 通常没有明确写出,所以它可以用十六进制表示,就像

0x 04 C1 1D B7

随意计算 1 和 0,但您会发现它们与多项式匹配,其中 1 是位 0(或第一位),x 是位 1(或第二位) .

为什么是这个多项式?因为需要有一个给定多项式的标准,并且该标准是由 IEEE 802.3 制定的。此外,要找到一个能有效检测不同误码的多项式也非常困难。

您可以将 CRC-32 视为一系列“无进位的二进制算术”,或者基本上是“XOR 和移位运算”。这在技术上称为多项式算术。

为了更好地理解它,想想这个乘法:

(x^3 + x^2 + x^0)(x^3 + x^1 + x^0)
= (x^6 + x^4 + x^3
 + x^5 + x^3 + x^2
 + x^3 + x^1 + x^0)
= x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0

如果我们假设 x 是以 2 为底,那么我们得到:

x^7 + x^3 + x^2 + x^1 + x^0

为什么?因为 3x^3 是 11x^11 (但我们只需要 1 或 0 个前置数字)所以我们结转:

=1x^110 + 1x^101 + 1x^100          + 11x^11 + 1x^10 + 1x^1 + x^0
=1x^110 + 1x^101 + 1x^100 + 1x^100 + 1x^11 + 1x^10 + 1x^1 + x^0
=1x^110 + 1x^101 + 1x^101          + 1x^11 + 1x^10 + 1x^1 + x^0
=1x^110 + 1x^110                   + 1x^11 + 1x^10 + 1x^1 + x^0
=1x^111                            + 1x^11 + 1x^10 + 1x^1 + x^0

但是数学家改变了规则,使其成为 mod 2。所以基本上任何二进制多项式 mod 2 都只是没有进位或 XOR 的加法。所以我们的原始方程如下所示:

=( 1x^110 + 1x^101 + 1x^100 + 11x^11 + 1x^10 + 1x^1 + x^0 ) MOD 2
=( 1x^110 + 1x^101 + 1x^100 +  1x^11 + 1x^10 + 1x^1 + x^0 )
= x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0 (or that original number we had)

我知道这是一种信念的飞跃,但这超出了我作为一名线路程序员的能力。如果你是一名核心 CS 学生或工程师,我会挑战打破这一点。每个人都将从这种分析中受益。

所以要制定一个完整的例子:

   Original message                : 1101011011
   Polynomial of (W)idth 4         :      10011
   Message after appending W zeros : 11010110110000

现在我们使用 CRC 算法将增强消息除以 Poly。这是和以前一样的划分:

            1100001010 = Quotient (nobody cares about the quotient)
       _______________
10011 ) 11010110110000 = Augmented message (1101011011 + 0000)
=Poly   10011,,.,,....
        -----,,.,,....
         10011,.,,....
         10011,.,,....
         -----,.,,....
          00001.,,....
          00000.,,....
          -----.,,....
           00010,,....
           00000,,....
           -----,,....
            00101,....
            00000,....
            -----,....
             01011....
             00000....
             -----....
              10110...
              10011...
              -----...
               01010..
               00000..
               -----..
                10100.
                10011.
                -----.
                 01110
                 00000
                 -----
                  1110 = Remainder = THE CHECKSUM!!!!

除法产生一个商,我们将其丢弃,并产生一个余数,即计算出的校验和。这结束了计算。通常,校验和会被附加到消息中并传输结果。在这种情况下,传输将是:11010110111110。

仅使用 32 位数字作为除数,并将整个流作为除数。扔掉商并保留余数。在邮件末尾添加剩余部分,您就有了 CRC32。

平均评价:

         QUOTIENT
        ----------
DIVISOR ) DIVIDEND
                 = REMAINDER
  1. 取前 32 位。
  2. 移位位
  3. 如果 32 位小于 DIVISOR,则转到步骤 2。
  4. DIVISOR 异或 32 位。转到第 2 步。

(请注意,流必须可被 32 位整除,否则应进行填充。例如,必须填充 8 位 ANSI 流。同样在流结束时,停止除法。)

【讨论】:

  • 最后为“Average Guy Review”+1 - 也许考虑将这个权利移到顶部 - 一种 TL;博士:P
  • @abstractnature 请记住,我们正在划分多项式,而不仅仅是二进制数。我们不能做“正常”减法,因为我们不能从 $x^{n+1}$; 中“借”$x^n$;它们是不同种类的东西。此外,由于位只有 0 或 1,-1 甚至是什么?真的,我们正在研究多项式环,其系数在 $Z/2Z$ 域中,它只有两个元素,0 和 1,其中 $1+1=0$。通过将系数放在一个域中,多项式就形成了所谓的欧几里得域,它基本上只是允许我们首先明确定义我们正在尝试做的事情。
  • 只是为了澄清实际的多项式是 100000100110000010001110110110111 = 0x104C11DB7。 MSB 是隐含的,但在实现中仍应考虑在内。因为多项式需要 33 位长(因此余数可以是 32 位长),所以它总是会被设置,所以有些人忽略了 MSB。
  • x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0 ... If we assume x is base 2 then we get: x^7 + x^3 + x^2 + x^1 + x^0。这不是数学的运作方式。多项式的系数是 mod(2) 或 GF(2),x 被单独留下,导致 x^6 + x^5 + x^4 + x^3 + x^2 + x^1 + x^ 0(因为 3 mod(2) = 1)。 Tack the remainder on the end of your message - 从技术上讲,从附加到消息的 0 位中减去余数,但由于这是 mod(2) 数学,加法和减法都与 XOR 相同,零位与余数异或与余数相同。
  • @MarcusJ - Why did you append four 0s though? - 计算 crc 的软件算法有效地附加了 0,即使它并不明显。如果使用长手除法显示 CRC 计算,则需要附加 0 以使除法示例正确显示。
【解决方案3】:

除了*的Cyclic redundancy checkComputation of CRC 文章之外,我还发现一篇题为Reversing CRC - Theory and Practice* 的论文是一个很好的参考。

基本上有三种计算 CRC 的方法:代数方法、面向位的方法和表驱动的方法。在Reversing CRC - Theory and Practice* 中,这三种算法/方法中的每一种都在理论上进行了解释,附录中附有 C 编程语言中 CRC32 的实现。

* PDF 链接
逆转 CRC – 理论与实践。
HU 柏林公开报告
SAR-PR-2006-05
2006 年 5 月
作者:
Martin Stigge、Henryk Plötz、Wolf Müller、Jens-Peter Redlich

【讨论】:

  • 嗨,你能详细说明一下吗?
【解决方案4】:

对于 IEEE802.3,CRC-32。将整个消息视为串行比特流,在消息末尾附加 32 个零。接下来,您必须反转消息的每个字节的位,并对前 32 位进行 1 的补码。现在除以 CRC-32 多项式 0x104C11DB7。最后,您必须对该除法位的 32 位余数进行 1 的补码,即取反余数的 4 个字节中的每一个。这成为附加到消息末尾的 32 位 CRC。

这个奇怪过程的原因是第一个以太网实现会一次序列化一个字节的消息,并首先传输每个字节的最低有效位。串行比特流然后经过串行 CRC-32 移位寄存器计算,在消息完成后简单地补充并通过线路发送出去。对消息的前 32 位进行补码的原因是,即使消息全为零,也不会得到全零的 CRC。

【讨论】:

  • 这是迄今为止最好的答案,尽管我会将 'bit-reverse each of the 4 bytes' 替换为 'bit-reverse the 4 bytes,将它们视为一个实体',例如'abcdefgh ijklmnop qrstuvwx yzABCDEF' 到 'FEDCBAzy xwvutsrq ponmlkji hgfedcba'。另请参阅:CRC-32 hash tutorial - AutoHotkey Community.
  • 嗨,什么“信息”确切;你反转了吗? *.com/questions/62168128/…
【解决方案5】:

我在这里发布了一篇关于 CRC-32 哈希的教程: CRC-32 hash tutorial - AutoHotkey Community

在这个例子中,我演示了如何计算 'ANSI'(每个字符 1 个字节)字符串 'abc' 的 CRC-32 哈希:

calculate the CRC-32 hash for the 'ANSI' string 'abc':

inputs:
dividend: binary for 'abc': 0b011000010110001001100011 = 0x616263
polynomial: 0b100000100110000010001110110110111 = 0x104C11DB7

start with the 3 bytes 'abc':
61 62 63 (as hex)
01100001 01100010 01100011 (as bin)

reverse the bits in each byte:
10000110 01000110 11000110

append 32 0 bits:
10000110010001101100011000000000000000000000000000000000

XOR (exclusive or) the first 4 bytes with 0xFFFFFFFF:
(i.e. flip the first 32 bits:)
01111001101110010011100111111111000000000000000000000000

next we will perform 'CRC division':

a simple description of 'CRC division':
we put a 33-bit box around the start of a binary number,
start of process:
if the first bit is 1, we XOR the number with the polynomial,
if the first bit is 0, we do nothing,
we then move the 33-bit box right by 1 bit,
if we have reached the end of the number,
then the 33-bit box contains the 'remainder',
otherwise we go back to 'start of process'

note: every time we perform a XOR, the number begins with a 1 bit,
and the polynomial always begins with a 1 bit,
1 XORed with 1 gives 0, so the resulting number will always begin with a 0 bit

'CRC division':
'divide' by the polynomial 0x104C11DB7:
01111001101110010011100111111111000000000000000000000000
 100000100110000010001110110110111
 ---------------------------------
  111000100010010111111010010010110
  100000100110000010001110110110111
  ---------------------------------
   110000001000101011101001001000010
   100000100110000010001110110110111
   ---------------------------------
    100001011101010011001111111101010
    100000100110000010001110110110111
    ---------------------------------
         111101101000100000100101110100000
         100000100110000010001110110110111
         ---------------------------------
          111010011101000101010110000101110
          100000100110000010001110110110111
          ---------------------------------
           110101110110001110110001100110010
           100000100110000010001110110110111
           ---------------------------------
            101010100000011001111110100001010
            100000100110000010001110110110111
            ---------------------------------
              101000011001101111000001011110100
              100000100110000010001110110110111
              ---------------------------------
                100011111110110100111110100001100
                100000100110000010001110110110111
                ---------------------------------
                    110110001101101100000101110110000
                    100000100110000010001110110110111
                    ---------------------------------
                     101101010111011100010110000001110
                     100000100110000010001110110110111
                     ---------------------------------
                       110111000101111001100011011100100
                       100000100110000010001110110110111
                       ---------------------------------
                        10111100011111011101101101010011

we obtain the 32-bit remainder:
0b10111100011111011101101101010011 = 0xBC7DDB53

note: the remainder is a 32-bit number, it may start with a 1 bit or a 0 bit

XOR the remainder with 0xFFFFFFFF:
(i.e. flip the 32 bits:)
0b01000011100000100010010010101100 = 0x438224AC

reverse bits:
bit-reverse the 4 bytes (32 bits), treating them as one entity:
(e.g. 'abcdefgh ijklmnop qrstuvwx yzABCDEF'
to 'FEDCBAzy xwvutsrq ponmlkji hgfedcba':)
0b00110101001001000100000111000010 = 0x352441C2

thus the CRC-32 hash for the 'ANSI' string 'abc' is: 0x352441C2

【讨论】:

【解决方案6】:

然后总是有 Rosetta Code,它显示了用几十种计算机语言实现的 crc32。 https://rosettacode.org/wiki/CRC-32 并链接到许多解释和实现。

【讨论】:

【解决方案7】:

为了减少 crc32 以采取提醒您需要:

  1. 反转每个字节的位
  2. xor 前四个字节与 0xFF(这是为了避免前导 0 出现错误)
  3. 在末尾添加填充(这是为了使最后 4 个字节参与哈希)
  4. 计算提醒
  5. 再次反转位
  6. 再次异或结果。

在代码中是:


func CRC32 (file []byte) uint32 {
    for i , v := range(file) {
        file[i] = bits.Reverse8(v)
    }
    for i := 0; i < 4; i++ {
        file[i] ^= 0xFF
    }

    // Add padding
    file = append(file, []byte{0, 0, 0, 0}...)
    newReminder := bits.Reverse32(reminderIEEE(file))

    return newReminder ^ 0xFFFFFFFF
}

reminderIEEE 是 GF(2)[x] 上的纯提醒

【讨论】:

  • 我有一点(双关语)理解这个问题? *.com/questions/62168128/…
  • 嘿@bluejayke,检查这个库github.com/furstenheim/sparse_crc32/blob/master/main.go 它实现了稀疏文件的crc32,你可以在那里看到计算的细节。它没有经过优化,因此比普通实现更容易遵循。可能你不明白的是GF(2)[x]部分。基本上x^3 + x 表示1010,x ^4 + x + 1 表示10011。然后你需要进行除法,例如x ^3 + x 是 x * (x^2 + 1)。所以x^3 +x对x的提醒是0,但是在x^2上就是x^2*x+x,也就是提醒会是x。
  • @bluejayke andremindIEEE 表示针对一个众所周知的多项式的提醒,即 IEEE 多项式
  • 您好,再次感谢您的回复。我只是想了解(出于javascript目的)多项式中的“x”表示什么。 “x”是我在这里缺少的东西的某种代号吗?这里有很多术语让我感到困惑,我以前从未听说过 CRC32,即使经过搜索,我也无法找到它的实际解释。例如,对于 PNG,它说我需要采用“每个块的 CRC”,这是否意味着“对于块中的所有数据”?但是我如何“插入”多项式呢? “x”代表什么?此外,当它说 x^32 时,是像 Math.pow(x, 32) 还是按位 ^
  • 嗨@bluejayke,x 是一种使计算变得容易的抽象。预计不会被任何东西取代。 x^2 我的意思是 x * x,作为形式乘法。在这里chrisballance.com/wp-content/uploads/2015/10/CRC-Primer.html 你可以找到一个很好的解释。我试图用我的答案来填补除法(在那个链接中)和实际计算之间的差距