ByteScope

Java JAR 檔案格式

Java Archive——把編譯後的 .class 檔與一份 manifest 打包起來的 ZIP。

MIME 類型
application/java-archive
副檔名
.jar
分類
壓縮與封存

Magic number(檔案簽章)

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

ZIP 內含 'META-INF/MANIFEST.MF' 以及一個以上的 '.class' 項目。

關於 Java JAR 格式

JAR 是在 ZIP 之上疊了一層慣例:一個 META-INF 目錄,裡面的 MANIFEST.MF 描述這個壓縮檔——可執行 JAR 的主類別、class path、模組名稱,而在簽署過的 JAR 裡還有每個項目的摘要值。ZIP 本身沒有任何改變,所以任何壓縮軟體都打得開 JAR,這個格式也才會被 WAR、EAR、Spring Boot fat JAR,以及早年的 Android APK 重複拿去用。要注意的是這層慣例也包含項目順序:串流式讀取端只會在檔案最前面找 manifest,找不到就很快放棄。

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

位移長度欄位意義
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 和兩個大小都是 0,而這完全沒有問題。
104Last modified time and date位移 10 是 MS-DOS 打包的時間、位移 12 是日期:解析度兩秒、沒有時區、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 dump 裡是個雖不嚴謹但很快的線索。
30variableFirst entry nameJAR 實務上宣告自己身分的地方:先是 META-INF/,接著 META-INF/MANIFEST.MF。這個順序不是裝飾,因為串流式讀取端只會在檔案前段找 manifest。
-224End of central directory signature50 4B 05 06,沒有壓縮檔註解時剛好位於倒數第 22 個位元組。類別載入器從這裡讀中央目錄,所以它能從 60 MB 的 JAR 裡載入一個類別,而不必解壓縮其餘部分。
-64Offset of the central directory中央目錄的起點,從檔案開頭起算。前面貼了一段 shell script 的自解壓縮或可執行 JAR 照樣能用,因為讀取端容忍這段位移,但這裡的數值已經和原始檔案配置對不上了。

怎麼跟長得像的格式分開

容易搞混的怎麼分
ZIP位元組層級完全相同。讓一個 ZIP 成為 JAR 的,是中央目錄裡的 META-INF/MANIFEST.MF,通常還加上 .class 項目。把 JAR 改名成 .zip 什麼都沒變;把 ZIP 改名成 .jar,則會得到一個被 java -jar 以「沒有 main manifest attribute」為由拒收的檔案。
Android APKAPK 的出身就是 JAR,而且到現在還有 META-INF,所以兩者在壓縮軟體裡看起來很像。分界在內容:有 .dex 項目與 AndroidManifest.xml 就是 APK,只有 .class 項目與 MANIFEST.MF 就是 JAR,而且只有 APK 會在中央目錄前面帶著 'APK Sig Block 42' magic。
war / ear同樣的格式、不同的必備配置:WAR 把類別放在 WEB-INF/classes、程式庫放在 WEB-INF/lib,EAR 則裝著 application.xml 加上其他壓縮檔。servlet 容器看的是配置而不是副檔名,所以副檔名標錯的 WAR 是卡在「找不到 WEB-INF」,而不是卡在 ZIP 本身。
Spring Boot jar一個可執行的 JAR,自己的類別放在 BOOT-INF/classes,相依套件則是 BOOT-INF/lib 底下巢狀的 JAR,而 MANIFEST.MF 裡指名的 Main-Class 其實是一個啟動器。它用一般 JAR 的方式打得開,但應用程式類別不在類別載入器平常會找的位置,所以把它解開、直接跑主類別是行不通的。

常見的壞法

  • manifest 不在最前面了。用通用的 ZIP 工具重新打包 JAR,每個位元組的內容都保住了,順序卻沒了,串流式讀取端接著就回報「完全沒有 manifest」。這件事用 JDK 自己的 API 實測過:jar 工具寫出來的 JAR(先 META-INF/、再 META-INF/MANIFEST.MF)讀得到 manifest,而把 manifest 放在某個 class 檔之後、當成第二和第三個項目的兩種壓縮檔,讀出來都是 null。java -jar 照樣跑得動,因為它讀的是中央目錄而不是串流——所以同一個檔案,這樣用會過、那樣用會掛。
  • 尾巴被截斷。每個本地標頭都活著、從位移 0 看過去很健康,但 end of central directory 記錄不見了,而從檔尾起步的類別載入器連一個項目都列不出來。救援方式是往後掃 50 4B 03 04 重建目錄。
  • 重新打包之後簽章壞掉。簽署過的 JAR 會在 META-INF/MANIFEST.MF 裡替每個項目存一份摘要,旁邊還有一個簽章檔,所以做 shading、重新壓縮或合併都會改變摘要,JVM 會以安全性例外拒收,而不是報損壞。
  • 粗糙的合併製造出重複項目。把相依套件串成單一壓縮檔,可能產生兩個路徑相同的項目;ZIP 允許這件事,而類別載入器取第一個,所以哪一版的類別勝出取決於中央目錄裡的順序——一個檔案本身沒問題、缺陷卻看不見的狀況。

這個格式現在還在用嗎

JAR 是整個 JVM 生態系的散布單位:Maven Central 上的東西都是 JAR,建置工具解析出來的每個相依套件都是 JAR,每個伺服器端 Java 應用程式也都以它出貨——不管是單純的程式庫、給容器用的 WAR,還是自給自足的 Spring Boot 壓縮檔。沒有東西取代它——模組系統是在 JAR 裡加了一個 module-info.class,而不是換一個新容器;native-image 和 jlink 是從 JAR 產出執行檔,而不是在上游把它換掉。它失守的地方是用戶端交付:applet 沒了,Java Web Start 也跟著沒了,所以今天的 JAR 是伺服器端或建置期的產物。它歷久不衰的優勢是 ZIP 的中央目錄,讓類別載入器能從幾千個類別裡抽出一個,完全不碰其他部分。

在這個站上打開 Java JAR 檔

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

相關格式

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

格式開頭位元組副檔名
ZIP50 4B 03 04.zip
Android APK50 4B 03 04.apk
Java class 檔CA FE BA BE.class

常見問題

檔案的 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 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

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