ByteScope

ZIP ファイル形式

ZIP アーカイブ。docx/xlsx/pptx、jar、apk、epub の土台になっているコンテナ形式でもあります。

MIME タイプ
application/zip
拡張子
.zip
カテゴリ
アーカイブ・圧縮

マジックナンバー(ファイル署名)

0x00000000504B0304PK··'PK' local-file header

ZIP 形式について

ZIP は最も広く使われている汎用アーカイブ形式であり、そして重要なことに、ひとつの形式ファミリー(Office 文書、Java の JAR、Android の APK、EPUB 書籍)の中身を支えるコンテナでもあります。ローカルファイルエントリは PK に続いて 03 04 で始まります。

ヘッダーの構造(バイト単位)

オフセット長さフィールド意味
04Local file header signature50 4B 03 04。Phil Katz にちなむ PK に続いて 3 と 4 です。格納された各ファイルがこれで始まるので、複数ファイルのアーカイブには何個も含まれます。位置が固定されているのはオフセット 0 のものだけです。
42Version needed to extractリーダーの最低バージョンを 10 倍した値です。10 なら無圧縮格納のみ、20 なら deflate でほぼどこでも見かける値、45 なら ZIP64、63 なら AES か LZMA。ZIP の数値はすべてそうであるようにリトルエンディアンです。
62General purpose bit flagビット 0 はエントリが暗号化されていることを示します。ビット 3 は CRC と 2 つのサイズがここでは 0 で、データの後ろに続くことを意味します。ストリーミングで作られた ZIP の hex ダンプにゼロが並ぶのはこれが理由です。ビット 11 はファイル名が UTF-8 であることを宣言します。
82Compression method0 は無圧縮格納で、後ろに続くバイトがファイルそのものであることを意味します。8 は deflate。12 は bzip2、14 は LZMA、93 は Zstandard、99 は AES ですが、これらを受け付けるリーダーはずっと少なくなります。
102Last modified timeMS-DOS のパックされた時刻です。時が 5 ビット、分が 6 ビット、秒が 2 秒単位で 5 ビット。分解能は 2 秒で、タイムゾーンはまったくありません。展開したファイルのタイムスタンプが 1 時間ずれていることが多いのはこのためです。
122Last modified dateMS-DOS のパックされた日付です。年が 1980 年からの 7 ビット、月が 4 ビット、日が 5 ビット。1980 年より前は表現できないので、テープから起こしたファイルは 1980-01-01 に着地します。
144CRC-32非圧縮データの CRC-32 です。展開ツールが inflate 後に突き合わせるのがこの値で、壊れたエントリで出る CRC 失敗メッセージの正体でもあります。
184Compressed sizeヘッダと可変長フィールドの後ろに続く圧縮データのバイト数です。FF FF FF FF なら、本当の値は ZIP64 の拡張フィールドのほうにあります。
224Uncompressed size元のサイズで、ZIP64 の抜け道も同じです。general purpose のビット 3 が立っているときは、どちらのサイズフィールドも 0 と読めます。データの後ろへ移動させられているからです。
262File name length後ろに続く名前のバイト長で、これを n とします。名前はスラッシュ区切りで格納され、先頭にスラッシュが付くことはありません。したがって先頭がスラッシュのエントリはパストラバーサルの試みです。
282Extra field length拡張フィールドブロックのバイト長で、これを m とします。ZIP64 のサイズ、Unix のパーミッション、高精度のタイムスタンプ、そして macOS が書いたアーカイブではかなりの量のアラインメント用パディングを運びます。
30variableFile namen バイトのパスで、ヘッダ全体でいちばん役に立つものです。docx と apk と epub を分けているのはこれであり、この位置より手前では 3 つとも 1 バイトも違いません。
-224End of central directory signature50 4B 05 06。EOCD レコードの固定部分は 22 バイトなので、アーカイブコメントが空ならちょうど末尾から 22 バイトのところから始まります。コメントがあるとさらに後ろへ押しやられ、リーダーは後ろ向きに走査して探す必要があります。
-64Offset 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 JARJAR は、中央ディレクトリに 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 と混同しやすい形式です。先頭バイトを見比べてください。

形式先頭バイト拡張子
GZIP1F 8B.gz
Word 文書(docx)50 4B 03 04.docx
Android APK50 4B 03 04.apk
Java JAR50 4B 03 04.jar
EPUB50 4B 03 04.epub
7-Zip37 7A BC AF 27 1C.7z

よくある質問

ファイルのマジックナンバーとは何ですか?

ファイル先頭の数バイトで、名前が何であろうとその形式を示すものです。PNG は必ず 89 50 4E 47 で始まり、ZIP は 50 4B 03 04 で始まります。OS もブラウザも file コマンドも、拡張子を信用せずこのバイト列を読みます。だからファイル名を変えても中身の正体は変わりません。

マジックナンバーを自分で確認する方法は?

macOS や Linux なら xxd -l 16 ファイル名 で先頭 16 バイトが表示されます。Windows なら PowerShell で Format-Hex -Path ファイル名 -Count 16 です。あるいはこのサイトのファイル形式判別ツールにドロップすれば、ローカルでバイト列を読んで形式を教えてくれます。アップロードはありません。

2 つの形式が同じマジックナンバーを持つのはなぜ?

一方が他方の上に作られているからです。docx、xlsx、pptx、apk、jar、epub はすべて中身が ZIP アーカイブなので ZIP の署名で始まり、区別するには内部のエントリを見る必要があります。Java の class ファイルと macOS のユニバーサルバイナリはどちらも CA FE BA BE で始まり、直後のフィールドで見分けます。

マジックナンバーがまったくないファイルもある?

あります。XML、HTML、JSON、SVG、CSV といったテキストベースの形式には固定のバイト署名がないため、構造から判別します。先頭の <?xml 宣言、<svg 要素、あるいは { に続く引用符付きのキーなどです。これは確定ではなく推測なので、このリファレンスではそうした形式をプローブ判別として示しています。

マジックナンバーが一致すればそのファイルは安全?

いいえ。わかるのは形式であって、中身が無害かどうかではありません。正規の PDF や docx でも悪意のあるペイロードを含んでいることがあります。特に拡張子とバイト列が食い違うときに真の形式を知るのは有用な第一段階ですが、マルウェアスキャンではありません。

ZIP かもしれないファイルをお持ちですか?ファイル形式判別ツールで調べる — マジックナンバーをローカルで読み取ります。アップロードは一切ありません。またはすべての形式を見る。