bigywater.tech
Java · 182

JAR 文件:manifest、签名和多版本 JAR

JAR 里到底有什么?manifest 怎么写、签名怎么保护、多版本 JAR 怎么按 JDK 版本选类?这篇真实打包、签名、篡改和在三个 JDK 上运行,逐项验证(多版本 JAR 在工程化部分有对应的篇目)。

72 字节
manifest 每行的最大长度,超过会折行
SHA-384 digest error
签名后替换 class 的验证失败信息
17 / 21 / 21
JDK 17、21、27 上多版本 JAR 选中的版本目录

Manifest

实测 · 本机执行输出
  没有 Main-Class:no main manifest attribute, in app_nomain.jar
  jar 里的文件: ['META-INF/', 'META-INF/MANIFEST.MF', 'App.class']
  META-INF/MANIFEST.MF 的内容:
    Manifest-Version: 1.0
    Created-By: 21.0.12.1 (Homebrew)
    Main-Class: App
  java -jar app.jar: Exception in thread "main" java.lang.NoClassDefFoundError: Util

Class-Path

实测 · 本机执行输出
  同目录有 util.jar:App 运行,依赖 Util:hello from Util
  把 app_cp.jar 挪到子目录(util.jar 不在它旁边):Exception in thread "main" java.lang.NoClassDefFoundError: Util
  Class-Path 写得很长时,jar 工具把 manifest 里的行折成了 6 行,每行最多 72 字节(续行以一个空格开头):
    |Manifest-Version: 1.0
    |Main-Class: App
    |Class-Path: dependency-00.jar dependency-01.jar dependency-02.jar depend
    | ency-03.jar dependency-04.jar dependency-05.jar dependency-06.jar depen
    | dency-07.jar

签名

实测 · 本机执行输出
  jarsigner 输出: jar signed. | Warning: | The signer's certificate is self-signed.
  签名后 jar 里的文件: ['META-INF/MANIFEST.MF', 'META-INF/DEV.SF', 'META-INF/DEV.EC', 'META-INF/', 'App.class']
  jarsigner -verify: jar verified.
  META-INF/DEV.SF 的开头:
    Signature-Version: 1.0
    Created-By: 21.0.12.1 (Homebrew)
    SHA-384-Digest-Manifest: A7fVQmfYgpWmatD61P+7Zkg87YQ+ploPHOenIgw0Agsxg4I
     jNNnDeBbW1PfCVQUZ
  替换了 App.class 之后 jarsigner -verify: jarsigner: java.lang.SecurityException: SHA-384 digest error for App.class
  java -jar tampered.jar: Exception in thread "main" java.lang.SecurityException: SHA-384 digest error for App.class

多版本 JAR

实测 · 本机执行输出
  jar 创建: 成功
  jar 里的文件: ['META-INF/', 'META-INF/MANIFEST.MF', 'Main.class', 'Ver.class', 'META-INF/versions/17/', 'META-INF/versions/17/Ver.class', 'META-INF/versions/21/', 'META-INF/versions/21/Ver.class']
  manifest 里有 Multi-Release: True
17:Ver.name() = versions/17 的实现
21:Ver.name() = versions/21 的实现
27:Ver.name() = versions/21 的实现
  JDK 21,但 -Djdk.util.jar.enableMultiRelease=false:21:Ver.name() = base(Java 8 风格的基线实现)

分步演示

JAR 文件
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 17、21、27(jar、jarsigner、keytool 用 JDK 21),自签名证书
    • 没有验证:自签名的过期证书能否用来签名/验证(没有试)、时间戳签名(TSA)、签名的 jar 被加载时的权限/SecurityManager(已移除)、jar 的压缩与 zip64、嵌套 jar(Spring Boot 的 fat jar)、Class-Path 里的 URL 形式;多版本 JAR 只在 17 / 21 / 27 三个版本上验证
    京ICP备2026067502号-1京公网安备11010502063487号