ByteScope

Java JAR ファイル形式

Java Archive。コンパイル済みの .class ファイルとマニフェストを束ねた ZIP。

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

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

0x00000000504B0304PK··'PK' — it is a ZIP

ZIP 内に 'META-INF/MANIFEST.MF' と 1 つ以上の '.class' エントリがあります。

Java JAR 形式について

JAR は、ZIP の上に取り決めを 1 つ載せたものです。META-INF ディレクトリの MANIFEST.MF がアーカイブを説明します。実行可能 JAR ならメインクラス、クラスパス、モジュール名、そして署名済み JAR なら全エントリのダイジェスト。ZIP としては他に何も変わらないので、どんなアーカイブツールでも JAR を開けますし、この形式が WAR や EAR、Spring Boot の fat JAR、そして歴史的には Android の APK にまで使い回されてきたのもそのためです。落とし穴は、その取り決めにエントリの順序まで含まれていること。ストリーミングで読む実装はマニフェストをファイルの一番前に探しに行き、そこに無ければあっさり諦めます。

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

オフセット長さフィールド意味
04Local file header signature50 4B 03 04。JAR はバイト単位で ZIP であり、オフセット 0 に Java の話は出てきません。どのエントリもこのヘッダで始まり、位置が固定されているのは最初のものだけです。
42Version needed to extract無圧縮なら 10、deflate なら 20。jar ツールが書いた JAR の最初のエントリは META-INF/ というディレクトリで、これは無圧縮なので、中身が全部 deflate されているファイルでもこのフィールドはよく 10 と読めます。
62General purpose bit flag0x0800(ビット 11)はエントリ名が UTF-8 であることを示し、JDK の jar ツールはこれを立てます。ビット 3、つまりサイズをデータの後ろへ押しやるストリーミング用のフラグは、その場で JAR を生成するサーバーの出力に現れます。
82Compression method0 が無圧縮、8 が deflate。META-INF/ のようなディレクトリのエントリは長さ 0 で無圧縮格納されるので、JAR の最初のエントリが CRC もサイズ 2 つもゼロなのは、どこも壊れていない正常な姿です。
104Last modified time and dateMS-DOS のパック形式で、時刻がオフセット 10、日付が 12。分解能 2 秒、タイムゾーンなし、1980 年より前は表現できません。再現可能ビルドはこれを定数に固定するので、やたら多くの JAR が 1980-01-01 や 2010-01-01 生まれを名乗っています。
144CRC-32非圧縮エントリの CRC-32 で、圧縮後サイズが 18、非圧縮サイズが 22。署名済み JAR では、これらは META-INF の SHA ダイジェストとは独立なので、CRC は通るのに署名検証には失敗するエントリがあり得ます。
262File name lengthオフセット 30 にある名前のバイト長。jar ツールが書いた JAR なら 9(META-INF/ の長さ)で、hex ダンプ上の非公式ながら手早い目印になります。
30variableFirst entry name実務上、JAR が自分の正体を名乗る場所です。META-INF/ に続いて META-INF/MANIFEST.MF。この順序は飾りではありません。ストリーミングで読む実装は、マニフェストをファイルの先頭部分でしか探さないからです。
-224End of central directory signature50 4B 05 06。アーカイブコメントが無ければ末尾からちょうど 22 バイトの位置です。クラスローダーはここからセントラルディレクトリを読みます。60 MB の JAR から 1 クラスだけを、残りを展開せずに読み込めるのはそのおかげです。
-64Offset of the central directoryセントラルディレクトリの開始位置を、ファイル先頭から数えたもの。シェルスクリプトを前置きした自己展開型・実行可能型の JAR が動くのは、読み手がそのずれを許容しているからで、この値自体は生のファイル配置と一致しなくなっています。

似た形式との見分け方

紛らわしい形式見分け方
ZIPバイトレベルでは同一です。ZIP を JAR にしているのは、セントラルディレクトリにある META-INF/MANIFEST.MF と、通常は .class エントリの存在です。JAR を .zip にリネームしても何も変わりませんし、ZIP を .jar にリネームすれば、java -jar が「メインマニフェスト属性が無い」と言って拒否するファイルができるだけです。
Android APKAPK は JAR として生まれた経緯があり、今も META-INF を持つので、アーカイブビューアでは似て見えます。分かれ目は中身で、.dex エントリと AndroidManifest.xml があれば APK、.class エントリと MANIFEST.MF だけなら JAR。そしてセントラルディレクトリの前に 'APK Sig Block 42' のマジックナンバーを持つのは APK だけです。
war / ear形式は同じで、要求されるレイアウトが違います。WAR はクラスを WEB-INF/classes、ライブラリを WEB-INF/lib に置き、EAR は application.xml と他のアーカイブを収めます。サーブレットコンテナが見るのは拡張子ではなくレイアウトなので、名前を間違えた WAR は ZIP まわりではなく WEB-INF が無いことで失敗します。
Spring Boot jar自分のクラスを BOOT-INF/classes に置き、依存を BOOT-INF/lib の中に入れ子の JAR として持つ実行可能 JAR で、MANIFEST.MF の Main-Class に指定されているのはランチャーです。普通の JAR として開けますが、アプリケーションのクラスがクラスローダーの通常の探索先にないので、展開してメインクラスを直接実行しても動きません。

よくある壊れ方

  • マニフェストが先頭でなくなった。汎用の ZIP ツールで JAR を作り直すと中身のバイトはすべて保たれますが順序は失われ、ストリーミングで読む実装は「マニフェストが無い」と報告します。JDK 自身の API で測ってみたところ、jar ツールが書いた JAR(META-INF/ の次に META-INF/MANIFEST.MF)はマニフェストを返し、class ファイルの後ろにマニフェストを 2 番目・3 番目のエントリとして置いたアーカイブはどちらも null を返しました。java -jar はそれでも動きます。ストリーミングではなくセントラルディレクトリを読むからです。同じファイルが、読み方によって動いたり失敗したりするわけです。
  • 末尾の切れ。ローカルヘッダはすべて残っていてオフセット 0 から見れば健全に見えますが、end-of-central-directory レコードが失われており、末尾から読み始めるクラスローダーはエントリを 1 つも列挙できません。復旧は 50 4B 03 04 を前方に探してディレクトリを組み直す作業です。
  • 再パッケージ後に署名が壊れた。署名済み JAR は META-INF/MANIFEST.MF に全エントリのダイジェストを持ち、その隣に署名ファイルを置きます。shade したり圧縮し直したりマージしたりするとダイジェストが変わり、JVM は破損ではなくセキュリティ例外としてアーカイブを拒否します。
  • 雑なマージで生まれた重複エントリ。依存を 1 つのアーカイブに連結すると、同じパスのエントリが 2 つできることがあります。ZIP はこれを許し、クラスローダーは最初の 1 つを採るので、どのバージョンのクラスが勝つかはセントラルディレクトリ上の順序で決まります。ファイルとしては正当なのに、目に見えない欠陥を抱えている状態です。

この形式の現在地

JAR は JVM の生態系まるごとにとっての配布単位です。Maven Central にあるものはすべて JAR、ビルドツールが解決する依存もすべて JAR、サーバーサイドの Java アプリケーションも、素のライブラリであれコンテナ用の WAR であれ自己完結した Spring Boot のアーカイブであれ、JAR として出荷されます。取って代わったものはありません。モジュールシステムは新しいコンテナではなく JAR の中の module-info.class を足しましたし、native-image や jlink も上流を置き換えるのではなく JAR から実行ファイルを作ります。地盤を失ったのはクライアント側の配信です。アプレットは消え、Java Web Start も一緒に消えたので、今日の JAR はサーバーかビルド時の成果物です。生き続けている強みは ZIP のセントラルディレクトリで、クラスローダーが何千ものクラスの中から 1 つだけを、残りに触れずに取り出せます。

Java JAR ファイルをこのサイトで開く

どれもブラウザ内で動きます。ファイルが端末から出ることはありません。

関連する形式

Java JAR と混同しやすい形式です。先頭バイトを見比べてください。

形式先頭バイト拡張子
ZIP50 4B 03 04.zip
Android APK50 4B 03 04.apk
Java class ファイルCA FE BA BE.class

よくある質問

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

ファイル先頭の数バイトで、名前が何であろうとその形式を示すものです。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 でも悪意のあるペイロードを含んでいることがあります。特に拡張子とバイト列が食い違うときに真の形式を知るのは有用な第一段階ですが、マルウェアスキャンではありません。

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