ByteScope

BZIP2 檔案格式

bzip2 壓縮的資料,使用 Burrows-Wheeler 區塊排序演算法。

MIME 類型
application/x-bzip2
副檔名
.bz2
分類
壓縮與封存

Magic number(檔案簽章)

0x00000000425A68BZh'BZh' header

關於 BZIP2 格式

bzip2 用 Burrows-Wheeler 轉換,壓縮率比 gzip 好但速度較慢,在 Unix 的原始碼 tarball 上很常見。標頭是 'BZh' 後面接著一位數的區塊大小。那四個位元組之後,這個檔案就完全不再以位元組為單位了:區塊、霍夫曼表和編碼符號是一個位元一個位元打包上去的,完全沒有對齊 — 這就是為什麼只有第一個區塊落在位元組邊界上,也是為什麼 hex 編輯器看得到 bzip2 檔案的開頭,卻看不懂它的中段。補償是每個區塊互相獨立,所以損壞被關在局部,而且真的救得回來。

檔頭結構,一個位元組一個位元組看

位移長度欄位意義
02'BZ' magic42 5A。兩個可列印的字母,這在二進位格式裡不太尋常,也是為什麼 'BZh' 在 hex dump 裡會以看得懂的文字出現。
21Version byte'h'(0x68),代表霍夫曼編碼。從 0.9.0 以來這個工具就只產出這一個版本,而解碼器也不接受別的 — 所以那個認得出來的三位元組前綴,其實是 magic 加版本,不只是 magic。
31Block-size digit一個 ASCII 數字 '1' 到 '9',以 100 kB 為單位給出區塊大小 — '9' 是 0x39,代表 900 kB,也是預設值。它決定壓縮、更重要的是解壓縮時的記憶體用量,所以寫入端選定一次之後,每個讀取端都必須照辦。
46First block magic31 41 59 26 53 59,也就是圓周率的位數以壓縮十進位打包。它只有在這裡、也就是第一個區塊的開頭是位元組對齊的;後面每個區塊的同一組值,都落在前一個區塊剛好結束的那個位元位置上。
104Block CRC-32這個區塊未壓縮內容的檢查碼,一樣只有第一個區塊是位元組對齊的。逐區塊的檢查碼正是讓部分救援有意義的原因:讀取端能明確指出是哪一個區塊壞了。
14variableRandomised flag and BWT origin pointer一個已淘汰的旗標位元(本世紀寫出來的東西一律是 0),後面接一個 24 位元的指標,指向 Burrows-Wheeler 矩陣裡能還原原始順序的那一列。從這裡開始就沒有任何東西是位元組對齊的了:符號表、霍夫曼表、選擇器和編碼資料是一條連續的位元串流。
variable6End-of-stream magic17 72 45 38 50 90,也就是圓周率平方根的位數,標記後面不再有區塊。它落在接近檔尾的地方,但位元位移由它前面所有東西決定,所以它跟檔尾的距離不是固定的。
variable4Combined CRC-32把每個區塊的 CRC 一路合併起來的值,緊接在結束串流 magic 之後,後面再補零到七個位元,好讓檔案在位元組邊界上收尾。這是 bzip2 唯一的全檔完整性值。

怎麼跟長得像的格式分開

容易搞混的怎麼分
GZIP'BZh' 加一個數字對上 1F 8B。它們在腳本裡的用法完全相同,副檔名也一天到晚被互換,但除此之外毫無共通之處:gzip 是位元組對齊的標頭蓋在一條 deflate 串流上,bzip2 是四個位元組的標頭蓋在一條位元串流上,而且有逐區塊的檢查碼。
7-Zip反方向那個隨時會發生的誤判。7-Zip 自己的 magic 開頭是 37 7A,讀起來就是 '7z',而 7z 封存檔可以拿 bzip2 當內部壓縮方法 — 所以 'BZh' 這個字串會出現在 .7z 檔案裡面。只有位移 0 說了算,而 .7z 那裡是 37 7A BC AF 27 1C。
ZIPZIP 的壓縮方法 12 就是 bzip2,所以一個 ZIP 項目的資料,可能在它的區域檔案標頭與變動長度欄位之後立刻以 'BZh' 開頭。只要位移 0 讀到 50 4B 03 04,在檔案裡搜到 'BZh' 就斷定手上是 .bz2 就是錯的。
XZ在多數原始碼發佈上取代它的那個格式。XZ 開頭是 FD 37 7A 58 5A 00,壓縮率也更好;bzip2 剩下的優勢是它的區塊彼此獨立,而 xz 的區塊預設不是。
pbzip2/lbzip2平行壓縮器吐出來的是一串完整、彼此獨立的 bzip2 串流串接在一起,所以 'BZh' 會在檔案裡以位元組對齊的位置重複出現好幾次,而圓周率那組區塊 magic 每條串流出現一次。結果仍然是一個合法的 .bz2,一般的解壓縮器從頭讀到尾都沒問題。

常見的壞法

  • 截斷。切口之前每個完整的區塊都還解得出來,因為區塊之間互不相依。bzip2recover 利用的正是這一點:它在每一組圓周率區塊 magic 的位置把位元串流切開,把每個區塊各自寫成一個小小的 .bz2,所以受損的封存檔可以只賠掉一個區塊的資料。
  • 區塊裡翻掉一個位元。因為區塊是霍夫曼編碼的,損壞通常在走到結尾之前就讓解碼脫軌 — 報的是資料錯誤,不是檢查碼不符。真的解到結尾時,位移 10 的逐區塊 CRC 會抓到它,並指出是哪一個區塊壞了。
  • 文字模式傳輸。位元串流裡的換行轉換,會讓損壞位元組之後的所有東西整個位移八個位元,所以即使四個位元組的標頭完好、檔案也照樣被正確辨識,解碼還是幾乎立刻就失敗。
  • 手改過的區塊大小數字。把 '9' 改成 '1',得到的檔案在每個偵測器眼裡仍然像 bzip2,卻會在第一個區塊就失敗,因為解壓縮器是靠那一個位元組決定緩衝區大小的。
  • 串接起來的串流被截斷在中途。平行壓縮的封存檔是一個檔案裡好幾條串流;掉了尾巴只會掉最後一條,前面那些都解得乾乾淨淨 — 這就是為什麼下載到一半的 pbzip2 檔案,往往還是能吐出大部分內容。

這個格式現在還在用嗎

bzip2 的高光時刻大約是 1999 到 2010 年,那時它是原始碼 tarball 從 gzip 往上升級的明顯選項,而網路上大量典藏的軟體至今仍是 .tar.bz2 — 光這一點就保證解碼器會一直出貨下去。要用在新工作上,它兩邊被夾殺:xz 在相近甚至更好的解壓縮速度下明顯壓得更小,而 Zstandard 在 gzip 等級的壓縮率下壓得快得多,所以各家發行版好幾年前就轉走了。它上游也沒有動能;原始實作實質上有十年沒人維護。真正還替它說話的是那個區塊結構。區塊彼此獨立,代表壓縮用 pbzip2 就能輕鬆平行化,而且在常見的 Unix 壓縮工具裡獨一無二的是:受損的封存檔可以一個區塊一個區塊搶救,而不是碰到第一個壞位元組就整份報銷。

在這個站上打開 BZIP2 檔

這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。

相關格式

容易和 BZIP2 搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
GZIP1F 8B.gz
XZFD 37 7A 58 5A 00.xz
Zstandard28 B5 2F FD.zst
TAR75 73 74 61 72.tar

常見問題

檔案的 magic number 是什麼?

檔案開頭的前幾個位元組,不管檔名寫什麼,它標明的是真正的格式。PNG 一定以 89 50 4E 47 開頭,ZIP 則是 50 4B 03 04。作業系統、瀏覽器和 file 指令讀的都是這幾個位元組,而不是相信副檔名 — 這就是為什麼把檔案改名並不會改變它實際上是什麼。

我要怎麼自己檢查 magic number?

在 macOS 或 Linux 上,xxd -l 16 檔名 會印出前 16 個位元組;在 Windows 上,PowerShell 的 Format-Hex -Path 檔名 -Count 16。或者把檔案丟進本站的檔案類型辨識工具,它會在本機讀取位元組並告訴你是什麼格式,不會上傳任何東西。

為什麼兩種格式的 magic number 會一樣?

因為其中一種是建立在另一種之上。docx、xlsx、pptx、apk、jar 與 epub 骨子裡都是 ZIP 壓縮檔,所以全都以 ZIP 的簽章開頭;要分辨它們得看裡面的項目。Java class 檔和 macOS 的通用二進位檔都以 CA FE BA BE 開頭,靠緊接其後的欄位區分。

有完全沒有 magic number 的檔案嗎?

有。XML、HTML、JSON、SVG、CSV 這類以文字為基礎的格式沒有固定的位元組簽章,所以改用結構來辨識:開頭的 <?xml 宣告、<svg 元素,或是 { 後面接著帶引號的鍵。這種偵測是合理推測而非確定,因此本參考頁會把這些格式標示為結構偵測。

magic number 相符就代表檔案安全嗎?

不代表。它告訴你的是格式,不是內容是否無害 — 一個貨真價實的 PDF 或 docx 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

手上有檔案可能是 BZIP2?用檔案類型辨識工具查出來 — 它在本機讀取 magic number,不上傳任何東西。或者瀏覽所有格式。