ZIP ファイル形式
ZIP アーカイブ。docx/xlsx/pptx、jar、apk、epub の土台になっているコンテナ形式でもあります。
- MIME タイプ
- application/zip
- 拡張子
- .zip
- カテゴリ
- アーカイブ・圧縮
マジックナンバー(ファイル署名)
| 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。Phil Katz にちなむ PK に続いて 3 と 4 です。格納された各ファイルがこれで始まるので、複数ファイルのアーカイブには何個も含まれます。位置が固定されているのはオフセット 0 のものだけです。 |
| 4 | 2 | Version needed to extract | リーダーの最低バージョンを 10 倍した値です。10 なら無圧縮格納のみ、20 なら deflate でほぼどこでも見かける値、45 なら ZIP64、63 なら AES か LZMA。ZIP の数値はすべてそうであるようにリトルエンディアンです。 |
| 6 | 2 | General purpose bit flag | ビット 0 はエントリが暗号化されていることを示します。ビット 3 は CRC と 2 つのサイズがここでは 0 で、データの後ろに続くことを意味します。ストリーミングで作られた ZIP の hex ダンプにゼロが並ぶのはこれが理由です。ビット 11 はファイル名が UTF-8 であることを宣言します。 |
| 8 | 2 | Compression method | 0 は無圧縮格納で、後ろに続くバイトがファイルそのものであることを意味します。8 は deflate。12 は bzip2、14 は LZMA、93 は Zstandard、99 は AES ですが、これらを受け付けるリーダーはずっと少なくなります。 |
| 10 | 2 | Last modified time | MS-DOS のパックされた時刻です。時が 5 ビット、分が 6 ビット、秒が 2 秒単位で 5 ビット。分解能は 2 秒で、タイムゾーンはまったくありません。展開したファイルのタイムスタンプが 1 時間ずれていることが多いのはこのためです。 |
| 12 | 2 | Last modified date | MS-DOS のパックされた日付です。年が 1980 年からの 7 ビット、月が 4 ビット、日が 5 ビット。1980 年より前は表現できないので、テープから起こしたファイルは 1980-01-01 に着地します。 |
| 14 | 4 | CRC-32 | 非圧縮データの CRC-32 です。展開ツールが inflate 後に突き合わせるのがこの値で、壊れたエントリで出る CRC 失敗メッセージの正体でもあります。 |
| 18 | 4 | Compressed size | ヘッダと可変長フィールドの後ろに続く圧縮データのバイト数です。FF FF FF FF なら、本当の値は ZIP64 の拡張フィールドのほうにあります。 |
| 22 | 4 | Uncompressed size | 元のサイズで、ZIP64 の抜け道も同じです。general purpose のビット 3 が立っているときは、どちらのサイズフィールドも 0 と読めます。データの後ろへ移動させられているからです。 |
| 26 | 2 | File name length | 後ろに続く名前のバイト長で、これを n とします。名前はスラッシュ区切りで格納され、先頭にスラッシュが付くことはありません。したがって先頭がスラッシュのエントリはパストラバーサルの試みです。 |
| 28 | 2 | Extra field length | 拡張フィールドブロックのバイト長で、これを m とします。ZIP64 のサイズ、Unix のパーミッション、高精度のタイムスタンプ、そして macOS が書いたアーカイブではかなりの量のアラインメント用パディングを運びます。 |
| 30 | variable | File name | n バイトのパスで、ヘッダ全体でいちばん役に立つものです。docx と apk と epub を分けているのはこれであり、この位置より手前では 3 つとも 1 バイトも違いません。 |
| -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 がこの 6 つすべてに一致することはありません。 |
| 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 と最低 1 つの classes.dex を持ちます。そのマニフェストはテキストではなく Android のバイナリ XML なので、データが 03 00 08 00 で始まります。スキーム v2 以降で署名された APK は、最後のエントリと中央ディレクトリの間に 16 バイトのマーカー APK Sig Block 42 も持ちます。 |
| Java JAR | JAR は、中央ディレクトリに META-INF/MANIFEST.MF を列挙している ZIP です。Java 自身の JarInputStream は、マニフェストが 1 番目か 2 番目のエントリのときしか見つけられないので、どのビルドツールも先頭に書きます。マニフェストが後ろに埋まった .jar は ZIP としては開けても、マニフェストが無いかのように読み込まれます。 |
| GZIP | どちらも圧縮されているという理由で絶えず混同されますが、バイトレベルでは紛れようがありません。gzip は 1F 8B で、ディレクトリをまったく持たないストリーム 1 本を抱えます。.tar.gz は全体を展開しない限り一覧を出せませんが、ZIP は末尾の数百バイトから一覧を出せます。 |
よくある壊れ方
- 末尾での途中切れ。ローカルヘッダはすべて残るので hex ダンプはオフセット 0 から見て完璧に健全なのに、どのツールもファイルを開けません。リーダーは末尾の EOCD から読み始めるのに、それが消えているからです。復旧は 50 4B 03 04 を前方に走査し、ローカルヘッダからディレクトリを組み直すことになります。
- 先頭に付いた余分なバイト。自己展開のスタブは合法でよくあるものですが、.zip の名前で保存された HTML のエラーページも同じことをします。中央ディレクトリのオフセットが、間違った基準点から測られた状態になるのです。ずれ量を計算して自動補正するリーダーもあれば、全エントリを破損と報告するものもあります。
- テキストモード転送。PNG を壊すのと同じ改行変換が、圧縮データの中の 0D 0A を 0A に変えます。ヘッダは解析でき、エントリ名も正しく一覧に出るので、失敗は展開時の CRC 不一致としてしか表に出ません。
- 4 GiB と 65535 エントリの壁。ZIP64 を出力しない書き手は、32 ビットのサイズフィールドと 16 ビットのエントリ数をそれぞれの範囲で回り込ませます。アーカイブは整形式のままで、数値だけが単純に間違っています。300 MB を名乗るエントリを展開したら 4.3 GB になった、というのがこのバグです。
- ファイル名の文字化け。general purpose フラグのビット 11 ができる前は、名前は作成者のローカルコードページで書かれていました。Windows で作った日本語ファイル名だらけのアーカイブは、ファイルのバイトが 1 つも壊れていなくても Linux ではゴミとして一覧に出ます。
この形式の現在地
ZIP がただのアーカイブ形式でなくなってから、もう長い時間が経ちました。今やコンテナ形式であり、だからこそ消えようがありません。あらゆる OOXML 文書、あらゆる EPUB、JAR、APK、XPI、ODF ファイルは、内部の配置が決められた ZIP です。素のアーカイバとしては圧縮率で 7z・xz・Zstandard に 20 年負け続けていますし、Unix では tar.gz が勝ちました。ZIP が拡張フィールドを介して不格好にしか表現できないパーミッションとシンボリックリンクを、そのまま保てるからです。それでも ZIP が第一候補であり続ける理由はランダムアクセスです。ディレクトリが中身に混ざらず末尾にまとまっているので、リーダーは他に一切触れずにアーカイブの一覧を出し、1 ファイルだけ取り出せます。それこそが Android のランタイムやワープロが必要とするもので、圧縮された tar には決してできないことです。
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 |