ZIP 檔案格式
ZIP 壓縮檔 — 同時也是 docx/xlsx/pptx、jar、apk 與 epub 底層的容器格式。
- MIME 類型
- application/zip
- 副檔名
- .zip
- 分類
- 壓縮與封存
Magic number(檔案簽章)
| 0x00000000 | 504B0304 | PK·· | 'PK' local-file header |
關於 ZIP 格式
ZIP 是最廣泛使用的通用壓縮格式,而且關鍵在於:它是一整族格式的底層容器(Office 文件、Java JAR、Android APK、EPUB 電子書)。區域檔案項目以 'PK' 開頭,後面接著 03 04。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 4 | Local file header signature | 50 4B 03 04 — 'PK' 取自 Phil Katz,然後是 3 和 4。每個存進去的檔案都以這一組開頭,所以多檔案的壓縮檔裡有很多組;只有位移 0 那一組的位置是固定的。 |
| 4 | 2 | Version needed to extract | 解壓縮所需的最低讀取端版本乘以十:10 代表只有 stored 項目,20 代表 deflate、也是你幾乎到處都會看到的值,45 代表 ZIP64,63 代表 AES 或 LZMA。小端序,跟 ZIP 裡每個數字一樣。 |
| 6 | 2 | General purpose bit flag | 位元 0 標記這個項目已加密。位元 3 表示這裡的 CRC 和兩個大小都是零、真正的值排在資料後面,這就是為什麼串流產生的 ZIP 在 hex dump 裡是一片零。位元 11 宣告檔名是 UTF-8。 |
| 8 | 2 | Compression method | 0 是 stored,代表後面那些位元組就是檔案本身;8 是 deflate。12 是 bzip2、14 是 LZMA、93 是 Zstandard、99 是 AES,這些都存在,只是接受它們的讀取端少得多。 |
| 10 | 2 | Last modified time | MS-DOS 的封裝時間:5 個位元的小時、6 個位元的分鐘、5 個位元的秒(以兩秒為單位)。解析度是兩秒,而且完全沒有時區 — 這就是為什麼解出來的檔案時間戳那麼常差一小時。 |
| 12 | 2 | Last modified date | MS-DOS 的封裝日期:7 個位元的年(從 1980 起算)、4 個位元的月、5 個位元的日。1980 年以前的東西表達不出來,所以從磁帶封存來的檔案全部落在 1980-01-01。 |
| 14 | 4 | CRC-32 | 未壓縮資料的 CRC-32。解壓縮之後拿來比對的就是這個值,而受損項目上那句「CRC failed」,背後也是它。 |
| 18 | 4 | Compressed size | 標頭與變動長度欄位之後,跟著幾個位元組的壓縮資料。FF FF FF FF 代表真正的值放在 ZIP64 的額外欄位裡。 |
| 22 | 4 | Uncompressed size | 原始大小,一樣有 ZIP64 那個逃生口。只要通用旗標的位元 3 被設起來,這兩個大小欄位都會是零,因為它們被搬到資料後面去了。 |
| 26 | 2 | File name length | 後面那個檔名的位元組長度 — 就叫它 n。名字是用正斜線存的,而且開頭絕不會有斜線,所以一個以斜線開頭的項目,就是在嘗試路徑穿越。 |
| 28 | 2 | Extra field length | 額外欄位區塊的位元組長度 — 就叫它 m。它裝著 ZIP64 的大小、Unix 權限、高精度時間戳,而在 macOS 產出的壓縮檔裡,還裝著大量的對齊填充。 |
| 30 | variable | File name | n 個位元組的路徑,也是整份標頭裡最有用的東西:把 docx、apk 和 epub 區分開來的正是它,而這三者在這個位置之前一個位元組都不差。 |
| -22 | 4 | End of central directory signature | 50 4B 05 06。EOCD 記錄的固定部分是 22 個位元組,所以在壓縮檔註解為空的情況下,它剛好從檔尾往回數 22 個位元組開始。有註解就會把它再往前推,讀取端只好倒著掃描找它。 |
| -6 | 4 | Offset of the central directory | 中央目錄從哪裡開始,從檔案開頭算起,一樣受「註解為空」這個前提限制。讀取端是從這裡、而不是從位移 0 開始工作的 — 這也是為什麼一個第一份標頭健康得不得了的壓縮檔,仍然可能打不開。 |
怎麼跟長得像的格式分開
| 容易搞混的 | 怎麼分 |
|---|---|
| EPUB | 這一族裡最嚴格的測試,因為 EPUB 的 OCF 容器規格把位元組釘死了:第一個項目必須叫 'mimetype'、必須不壓縮存放、而且不能有額外欄位。所以 EPUB 位移 0 是 50 4B 03 04、位移 8 是方法 0、位移 26 是檔名長度 8、位移 28 是額外欄位長度 0、位移 30 是 'mimetype',位移 38 則是字串 application/epub+zip。一般的 ZIP 不可能六項全中。 |
| Word 文件(docx) | 到區域標頭為止一個位元組都不差;差別完全在中央目錄列出來的那些名字。OOXML 套件的壓縮檔根目錄必須有 [Content_Types].xml,而 Word 把它寫成第一個項目,所以位移 30 通常讀到的就是這個名字。接著 word/document.xml 代表 docx,xl/workbook.xml 代表 xlsx,ppt/presentation.xml 代表 pptx。 |
| Android APK | 同樣是沒有任何可辨識標頭位元組的 ZIP。APK 的壓縮檔根目錄有 AndroidManifest.xml 和至少一個 classes.dex,而那份 manifest 是 Android 的二進位 XML、不是文字,所以它的資料開頭是 03 00 08 00。用 v2 以上簽章方案簽過的 APK,還會在最後一個項目和中央目錄之間帶著 16 個位元組的標記 'APK Sig Block 42'。 |
| Java JAR | JAR 是一個中央目錄裡列著 META-INF/MANIFEST.MF 的 ZIP。Java 自己的 JarInputStream 只有在 manifest 是第一或第二個項目時才找得到它,所以每個建置工具都把它寫在最前面;manifest 被埋在後面的 .jar 仍然當 ZIP 打得開,載入時卻像是根本沒有 manifest。 |
| GZIP | 老是被搞混,因為兩者都是「壓縮過的」,但位元組層級根本沒東西好混:gzip 是 1F 8B,裝的是一條串流,完全沒有目錄。.tar.gz 不整份解壓縮就列不出內容,而 ZIP 從最後幾百個位元組就列得出來。 |
常見的壞法
- 尾巴被截斷。每一份區域標頭都還在,所以從位移 0 看下去 hex dump 健康得很,卻沒有東西打得開這個檔案 — 讀取端是從檔尾的 EOCD 開始的,而它不見了。要救就得往前掃描 50 4B 03 04,從區域標頭把目錄重建出來。
- 前面被塞了位元組。自解壓縮的執行檔前綴是合法而且常見的,但一頁被存成 .zip 名字的 HTML 錯誤訊息做的是同一件事:中央目錄裡的位移現在是從錯的基準量的。有些讀取端會自己算出偏移量修正,有些則把每一個項目都報成損壞。
- 文字模式傳輸。那套毀掉 PNG 的換行轉換,在這裡會把壓縮資料裡的 0D 0A 變成 0A。標頭照樣剖析得動、項目名稱也照樣列得出來,所以失敗只會在解壓縮的時候以 CRC 對不上的形式浮出來。
- 4 GiB 與 65535 個項目那兩道牆。從來不寫 ZIP64 的寫入端,會讓 32 位元的大小欄位和 16 位元的項目數各自對它們的範圍取模繞回去。壓縮檔仍然格式正確,數字就只是錯的:一個宣稱 300 MB、解出來卻是 4.3 GB 的項目,就是這個 bug。
- 檔名亂碼。在通用旗標的位元 11 出現之前,名字是用建立者當地的字碼頁寫的。一個在 Windows 上做的、裝滿日文檔名的壓縮檔,在 Linux 上列出來就是一團亂碼,即使檔案裡一個位元組都沒壞。
這個格式現在還在用嗎
ZIP 很久以前就不只是壓縮格式,而是變成了容器格式,這也是它哪裡都不會去的原因。每一份 OOXML 文件、每一本 EPUB、每一個 JAR、APK、XPI 和 ODF 檔案,都是一個帶著規定內部排版的 ZIP。純粹當壓縮工具來看,它的壓縮率被 7z、xz 和 Zstandard 壓著打已經二十年,而 Unix 上是 tar.gz 贏了,因為它保留得了權限和符號連結 — 那些東西 ZIP 只能靠額外欄位彆扭地表達。讓 ZIP 保住第一選擇位置的是隨機存取:因為目錄坐在檔尾、而不是跟資料交錯在一起,讀取端可以列出整個壓縮檔、單獨抽出其中一個檔案,完全不必碰其他部分。這正是 Android runtime 或文書處理軟體需要的,也正是壓過的 tarball 做不到的。
在這個站上打開 ZIP 檔
這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。
相關格式
容易和 ZIP 搞混的格式,可以比對它們的開頭位元組。
| 格式 | 開頭位元組 | 副檔名 |
|---|---|---|
| GZIP | 1F 8B | .gz |
| Word 文件(docx) | 50 4B 03 04 | .docx |
| Android APK | 50 4B 03 04 | .apk |
| Java JAR | 50 4B 03 04 | .jar |
| EPUB | 50 4B 03 04 | .epub |
| 7-Zip | 37 7A BC AF 27 1C | .7z |