Python CRC-32 问题

7 投票
2 回答
9668 浏览
提问于 2025-04-16 12:06

我正在写一个Python程序,目的是从一个6GB的bz2文件中提取数据。bz2文件由可以独立解密的数据块组成,所以我只需要找到一个数据块(这些块是由特定的标志位分隔的),然后在内存中创建一个临时的单块bz2文件,最后把它传给bz2.decompress函数。听起来很简单,对吧?

bzip2格式在文件末尾有一个crc32校验和。没问题,binascii.crc32来帮忙。但是等等,待校验的数据不一定在字节边界上结束,而crc32函数是针对完整的字节进行操作的。

我的计划是:对除了最后一个字节以外的所有字节使用binascii.crc32函数,然后用我自己的一个函数来更新计算出的crc,处理最后的1到7位。但是经过几个小时的编码和测试,我感到很困惑,我的疑问可以归结为这个问题:为什么crc32("\x00")不是0x00000000?根据维基百科的说法,它不是应该是吗?

你从0b00000000开始,然后填充32个0,再用0x04C11DB7进行多项式除法,直到前8位没有1为止,而这一步是立即完成的。你最后的32位就是校验和,怎么可能不是全零呢?

我在谷歌上搜索过答案,也查看了几个CRC-32实现的代码,但没有找到任何线索来解释为什么会这样。

2 个回答

3

除了可以一次性解压的 decompress 函数,bz2 模块还有一个类 BZ2Decompressor,它可以在你逐步输入数据时进行解压。这意味着它不需要关心文件结束时的校验和,只要在到达数据块的末尾时提供所需的数据。

举个例子,假设我已经找到了想要从文件中提取的数据块,并把它存储在一个 bitarray.bitarray 实例中(其他处理位的模块也可能可以用)。那么这个函数就可以用来解码它:

def bunzip2_block(block):
    from bz2 import BZ2Decompressor
    from bitarray import bitarray

    dummy_file = bitarray(endian="big")
    dummy_file.frombytes("BZh9")
    dummy_file += block

    decompressor = BZ2Decompressor()
    return decompressor.decompress(dummy_file.tobytes())

需要注意的是,bitarray 的 frombytestobytes 方法之前被称为 fromstringtostring

11

为什么crc32("\x00")的结果不是0x00000000呢?

CRC算法的基本原理是把输入的信息当作一个多项式,在GF(2)这个数学领域里,用一个固定的CRC多项式去除这个多项式,然后用余数作为最终的哈希值。

CRC-32在基本算法上做了一些修改:

  1. 每个字节里的位是反向处理的。比如,字节0x01被当作多项式x^7,而不是x^0。
  2. 信息的右边会加上32个零。
  3. 这个反向处理并且填充后的信息的前4个字节会和0xFFFFFFFF进行异或运算。
  4. 余数多项式会被反向处理。
  5. 余数多项式会再和0xFFFFFFFF进行异或运算。
  6. 另外,CRC-32的多项式,非反向形式是0x104C11DB7。

现在我们来计算一下单字节字符串0x00的CRC-32:

  1. 信息:0x00
  2. 反向处理:0x00
  3. 填充后:0x00 00 00 00 00
  4. 异或运算:0xFF FF FF FF 00
  5. 用0x104C11DB7除后的余数:0x4E 08 BF B4
  6. 异或运算:0xB1 F7 40 4B
  7. 反向处理:0xD2 02 EF 8D

所以,0x00的CRC-32结果是0xD202EF8D。
(你可以自己验证一下。)

撰写回答