Python 的 zlib.crc32() 與 binascii.crc32() 會計算 RFC 1952 所定義的 CRC-32/ISO-HDLC 校驗碼 —— 反射多項式 0xEDB88320、初始暫存器 0xFFFFFFFF,以及最終 XOR 0xFFFFFFFF —— 這會對 123456789 的 ASCII 位元組產生標準的檢查值 cbf43926。這兩個內建函式包裝了同一個 C 實作,並回傳相同的 32 位元整數,因此兩者之間的選擇主要是看哪一行的 import 較符合你的專案。CRC32 計算工具會在 UTF-8 文字或明確指定的十六進位位元組上計算相同的變體,將結果格式化為八個小寫的十六進位數字,並且完全在你的瀏覽器中執行,不會上傳任何輸入。將 Python 函式與這個線上工具搭配使用,是確認 Python 腳本、第三方檔案以及規格說明書在 CRC 變體與位元組序列上都一致的最可靠方式。本文會逐步說明精確的 Python 呼叫方式、會破壞跨版本比較的帶正負號與不帶正負號之間的陷阱,以及驗證 Python 結果與計算工具八位數十六進位輸出是否一致的精確步驟。

calculate crc32 in python
在 Python 中計算 CRC32 並比對每一個十六進位數字

Python 的 CRC32 函式實際上計算了什麼

zlib.crc32() 與 binascii.crc32() 都接受一個 bytes-like 物件,並回傳一個 32 位元整數。這個整數是整個輸入作為單一串流計算出來的 CRC —— 在 Python 中沒有獨立的文字模式或十六進位模式;呼叫端必須自行決定要餵入哪些位元組。直接傳入 str 會引發 TypeError,因此在呼叫函式之前,必須使用正確的編 codec 明確地將字串編碼。

zlib.crc32() 同時也接受一個選擇性的第二個參數,用來保存前一段資料的執行中 CRC。標準的累加模式看起來像 crc = zlib.crc32(b"first chunk"),接著 crc = zlib.crc32(b"second chunk", crc),然後使用 crc & 0xFFFFFFFF 對最終值進行遮罩。當酬載太大而無法一次放進記憶體,或從 socket 或檔案讀取器以串流方式抵達時,這個模式就非常有用。binascii.crc32() 並未提供第二個參數 —— 它一律從預設的初始值開始 —— 因此若要進行滾動式校驗和計算,請使用 zlib。

多項式以及初始與最終的 XOR 都已寫入 C 函式庫,無法從 Python 端變更。目前並沒有任何公開的 Python 函式可用於 CRC-32C (Castagnoli)、CRC-32/MPEG-2、CRC-32/Koopman、CRC-32/BZIP2 或 JAMCRC;這些都需要第三方函式庫或自行手寫的查詢表。如果某個規格對 123456789 的 ASCII 位元組所引用的檢查值不是 cbf43926,那麼 Python 的內建函式對該格式而言就算錯了變體,結果將永遠無法與規格相符。

在 Python 中執行 zlib.crc32 並取得八個十六進位數字

以下步驟會產生一個可攜、不帶正負號的 32 位元結果,並格式化為八個小寫的十六進位數字,這與 CRC32 計算工具以及大多數檔案格式所使用的輸出格式一致。

  1. 匯入 zlib 並將資料準備為 bytes。對 Python 字串呼叫 str.encode("utf-8"),或以 "rb" 二進位模式開啟檔案,這樣讀取時才會回傳原始位元組而非解碼後的文字。
  2. 呼叫 zlib.crc32(data) 以取得原始整數。如果要對多區塊串流進行雜湊,請傳入第二個參數帶上執行中的 CRC。
  3. 使用 & 0xFFFFFFFF 對結果進行遮罩。在 Python 2 中這個步驟是不可省的,在 Python 3 中則建議在所有平台上都這麼做,因為底層 C 型別可能是帶正負號的,負值將無法對應到標準的八位數十六進位格式。
  4. 使用 format(crc, "08x") 或 f 字串 f"{crc:08x}" 進行格式化,以產生八位數的小寫字串。小寫可以讓輸出在位元組層級上與所有以 ASCII 十六進位儲存校驗和的規格保持相容。
  5. 與已發佈的規格互相核對。123456789 的 ASCII 位元組必須產生 cbf43926;如果你的結果不是這樣,表示所用的變體或位元組有誤,無論如何重新格式化都無法修正。

用於快速健全性檢查的標準一行寫法是 hex(zlib.crc32(b"123456789") & 0xFFFFFFFF)。其結果為字串 0xcbf43926,與 CRC32 計算工具頁面以及 RFC 1952 範例中所引用的檢查值一致,證明 Python 的內建函式與線上工具實作了相同的變體。將這一行存成測試 fixture,並在每一個會接觸到你的程式碼路徑的直譯器上執行;它的成本幾乎為零,卻比任何自訂斷言更能抓到 bug。

將 Python 輸出與 CRC32 計算工具交叉核對

CRC32 計算工具的存在目的,正是讓你能逐位元組驗證 Python 腳本與外部檔案使用的是相同的定義與相同的輸入。以下是精確的工作流程。

  1. 確認規格所要求的 CRC 變體。就 Python 的內建函式而言,這就是 CRC-32/ISO-HDLC;計算工具實作了相同的變體,因此兩端相符即可證明變體是一致的。
  2. 決定你要餵入的是 UTF-8 文字還是原始位元組。當你在 Python 中使用了 str.encode("utf-8") 進行編碼時,請在計算工具中使用文字模式;當你讀取的是二進位檔案,或規格直接列出位元組序列時,請使用十六進位模式。
  3. 將相同的酬載貼到計算工具中並計算。它回傳的八位數十六進位值必須等於你腳本中 zlib.crc32(...) & 0xFFFFFFFF 所產生的值。
  4. 逐一比對全部八個數字,包含前導零。像 f43926 這種缺少前導 cb 的短碼比對,通常代表變體不符或前導零被吃掉,而不是雜湊碰撞。
  5. 使用計算工具的複製功能來複製結果。複製功能只會複製精確的 32 位元十六進位值,永遠不會包含輸入內容,因此可以放心地將這些數字貼到測試 fixture 或文件片段中。

如果兩邊的值不一致,最常見的原因是位元組編碼出現漂移:換行被統一成 CRLF、多了一個多餘的 UTF-8 BOM,或是規格中帶有 0x 前綴的十六進位數字會被計算工具刻意拒絕。請將計算工具切換到十六進位模式,並貼上你在 Python 中實際雜湊的位元組,以釐清是哪個變因出了問題。若需要更深入的排解,hex 檔 CRC 計算的逐步指南會以純十六進位的工作流程,示範同一個 ISO-HDLC 變體。

在 Python 中計算 CRC32 的常見陷阱

帶正負號與不帶正負號的差異,是導致比較失敗最常見的原因。在 Python 2 中,zlib.crc32() 會回傳範圍在 [-2**31, 2**31 - 1] 的帶正負號整數,因此像 0xCbf43926 這樣的值會以一個很大的負數呈現。在 Python 3 中,雖然不帶正負號的行為較為常見,但 C 擴充模組並不保證在所有組建下都一致 —— 使用 & 0xFFFFFFFF 進行遮罩就能完全消除這種模糊性,並在每一個直器、每一個平台、以及每一個 CI 執行器上產生相同的字串。

編碼錯誤是第二常見的問題。將 str 傳給 zlib.crc32() 會引發 TypeError,但若傳入 str.encode("utf-16") 或 str.encode("cp1252"),則會在不知情的狀況下雜湊出完全不同的位元組序列。CRC32 計算工具在文字模式下一律使用 UTF-8,因此即使是 UTF-16 編碼的酬載,即使兩端都計算了「同一個字串」,結果也不會相符。當通訊協定列出精確的位元組時(例如 gzip 標頭欄位、PNG chunk、或韌體 blob),請將計算工具切換到十六進位模式,並直接貼上這些位元組。諸如 0x 等前綴、非空白的分隔符、註解以及奇數長度的尾端 nibble 都會被刻意拒絕,而前導 00 位元組則會被保留,因為它們會影響結果。

當訊息是文字檔時,換行處理常讓人踩坑。在 Windows 上以 CRLF 儲存、又在 Linux 上重新存成 LF 的檔案,即使在編輯器中「看起來一模一樣」,也會產生兩個不同的 CRC。CRC32 計算工具不會對換行字元進行標準化,這對校驗和工具來說才是正確的行為;如果你需要的是標準化後的 CRC,請先做一次標準化,然後把同樣的標準化位元組分別餵給 Python 和計算工具。相同的原則也適用於尾端空白、UTF-8 檔案開頭的 BOM,以及檔尾有無換行之間的差異 —— 除非你在雜湊之前明確地去除,否則每個位元組都在計算範圍內。

CRC32 變體及其參數為何重要

不同的檔案格式與通訊協定雖然都掛著 CRC32 這個名字,卻在多項式、初始暫存器、反射輸入旗標以及最終 XOR 上各有不同的定義。根據 CRC RevEng 參數目錄(這是用來區分這些變體的實質參考來源),下表列出了 Python 開發者最可能遇到的一些公式,並附上 123456789 的 ASCII 位元組所對應、能唯一識別各變體的檢查值。

變體多項式(正常)初始值反射輸入反射輸出最終 XOR檢查值("123456789")
CRC-32/ISO-HDLC(Python 內建、gzip、ZIP、PNG)0x04C11DB70xFFFFFFFF0xFFFFFFFFcbf43926
CRC-32C(Castagnoli,iSCSI、SCTP、BTRFS)0x1EDC6F410xFFFFFFFF0xFFFFFFFFe3069283
CRC-32/MPEG-2(MPEG-2、H.264 Annex E)0x04C11DB70xFFFFFFFF0x000000000376e6e7
CRC-32/BZIP2(Bzip2 不含最終 XOR 的變體)0x04C11DB70xFFFFFFFF0xFFFFFFFFfc891918
JAMCRC(少見,某些遊戲模組工具)0x04C11DB70x000000000x000000002d02ef8c

如果某個規格對 123456789 引用了不同的檢查值,表示該系統使用的是不同的變體。CRC32 計算工具僅實作 CRC-32/ISO-HDLC,這正是 Python 內建函式所產生的,也是 gzip、ZIP 與 PNG 在磁碟上所儲存的。若想了解如何手動推導這些參數,逐步 CRC 演練使用了相同的反射多項式,並以相同的八位數十六進位輸出作為一個完整的範例。

用於驗證的 CRC32:失效之處

CRC32 只能偵測意外損壞。它無法證明檔案來自信任的發布者、無法授權命令,也無法保護憑證。其輸出具有線性特徵且容易遭到操弄,因此攻擊者即使不知道原始內容,也能修改承載資料,然後重新計算相符的 CRC32。對於任何包含惡意修改的威脅模型,請改用 SHA-256 等加密摘要搭配經過驗證的發布管道;如果雙方共用密鑰,則可改用 HMAC。CRC32 計算器會為任何傳入的位元組序列產生一個值,包括攻擊者自行選擇的值——因此,與計算器比對通過,只能證明意外錯誤的一致性,無法證明安全性或真實性。

實用原則如下:如果下載頁面同時發布 CRC32 和 SHA-256,請信任經簽章或受 TLS 保護來源驗證的 SHA-256,而後僅將 CRC32 作為快速的下載後健全性檢查,確認磁碟上的位元組與發布者雜湊處理的位元組相符。如果只有 CRC32,頂多只能將比對視為薄弱的完整性檢查。同樣的注意事項也適用於使用 CRC32 管控命令執行、驗證韌體或授權 API 呼叫——這些用途每一項都需要驗證,而不只是校驗碼。