bigywater.tech
Java · 434

Java 发布产物签名

发布产物的签名回答的是“这个文件是谁发布的、有没有被改过”。这篇真实签名并篡改一个 jar,对照 jarsigner 与运行时的反应,再用 PGP 分离签名演示为什么校验和不能代替签名,并量出签名的运行时开销。

SecurityException
改了已签名类的字符串后,jarsigner -verify 与 java -cp 运行都抛 SHA-384 digest error;未签名的 jar 上同样的改动无任何报错
verified
攻击者改类后用自己的证书重新签名,jarsigner -verify 仍然是 jar verified.(签名者是 CN=Attacker)
1.19 倍
签名后 jar 加载 1000 个类的耗时比值(76 ms → 90 ms)

签名与验证

实测 · 本机执行输出
  jarsigner 签名:rc=0;输出:jar signed.
  签名后条目 5 → 7 个,新增:META-INF/MYKEY.SF,META-INF/MYKEY.RSA;jar 大小 1144 → 3058 字节;MANIFEST.MF 里每个类文件一行摘要(JDK 21 默认 SHA-384):2 条,其中 SHA-384-Digest 2 条
  MYKEY.SF 头部:Signature-Version: 1.0 | Created-By: 21.0.12.1 (Homebrew) | SHA-384-Digest-Manifest: yAOlcfTLA+n6QNGRRbNdH3dzQ3cCVRC+MoHxdw2s/Ofq6LQ;MYKEY.RSA(PKCS#7 签名块)大小 1345 字节
  jarsigner -verify signed.jar:rc=0;jar verified. / This jar contains entries whose certificate chain is invalid. Reason: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target / This jar contains signatu
  加 -strict:rc=4(自签名证书与没有时间戳都会让 -strict 返回非零)
  未签名的 app.jar:rc=0;jar is unsigned.

篡改与重新签名

实测 · 本机执行输出
  (a) 改了已签名类里的字符串(同长度,hello-original → hello-tampered):jarsigner -verify rc=1;jarsigner: java.lang.SecurityException: SHA-384 digest error for p/Hello.class;java -cp 运行:Exception in thread "main" java.lang.SecurityException: SHA-384 digest error for p/Hello.class
  (a') 同样的改动,但 jar 没有签名:java -cp 运行:hello-tampered(没有任何报错,输出被改了)
  (b) 往已签名的 jar 里加一个没有签名的文件:jarsigner -verify rc=0;jar verified. / This jar contains unsigned entries which have not been integrity-checked. / This jar contains entries whose certificate chain is invalid. Reason: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to f;加 -strict rc=20
  (c) 删掉 META-INF/MYKEY.SF 与 .RSA:rc=0;jar is unsigned.;java -cp 运行:hello-original
  (d) 攻击者改了类、用自己的证书重新签名:jarsigner -verify rc=0;s        290 Mon Oct 05 20:13:10 CST 2026 META-INF/MANIFEST.MF / sm       445 Mon Oct 05 20:13:10 CST 2026 Main.class (and 1 more) / jar verified. / This jar contains entries whose certificate chain is invalid. Reason: PKIX path building failed: sun.security.p;签名者:CN=Attacker;java -cp 运行:hello-tampered
  (e) 用 SHA1 摘要与 SHA1withRSA 签名:签名 rc=0(jar signed. / The SHA1 algorithm specified for the -digestalg option is considered a security risk and is disabled. / The SHA1withRSA algori);验证 rc=0;The jar will be treated as unsigned, because it is signed with a weak algorithm that is now disabled.
      -certs 输出里与算法有关的行:Digest algorithm: SHA1 (disabled) / Signature algorithm: SHA1withRSA (disabled), 2048-bit key / WARNING: The jar will be treated as unsigned, because it is signed with a weak algorithm that is now disabled by the security property: / jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySi

校验和与 PGP 签名

实测 · 本机执行输出
  发布者密钥:RSA 2048,指纹 3efeae14848730b475645f16d75f382b6c8f1eae,key id d75f382b6c8f1eae;另一个无关密钥 key id 9c897dd80479c601
  app.jar 的分离签名 app.jar.asc:485 字节,首行 -----BEGIN PGP SIGNATURE-----,末行 -----END PGP SIGNATURE-----;app.jar 1144 字节(签名大小与 jar 大小无关)
  用发布者的公钥验证原始 jar:true;验证被篡改的 jar:false;用无关的公钥验证原始 jar:false
  攻击者同时替换 jar 与 .sha256 文件:下载到的 jar 的 SHA-256 86f2aeaff02dd8d1… 与替换后的 .sha256 文件内容 86f2aeaff02dd8d1… 相等:true(校验和检查通过);而 .asc 还是发布者的签名:false(签名检查失败)
  攻击者也替换 .asc(用自己的密钥 d0820345889b3e0c 签名):用攻击者的公钥验证 true,用发布者的公钥验证 false(签名通过与否取决于你信任哪把公钥)
  原始 jar 只翻转 1 个比特:SHA-256 前 16 位 20d6d5a8e621bc39 → 1816b9635a64e0af,签名验证 false

运行时开销

实测 · 本机执行输出
  1000 个类,每个新 JVM 里 Class.forName + 调用一次(9 次取中位数):未签名 76 ms(最小 74、最大 78),签名后 90 ms(最小 89、最大 91),比值 1.19
  jar 大小:未签名 291734 字节,签名后 405634 字节;MANIFEST.MF 里的摘要条目 1001 条

分步演示

发布产物签名
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12 的 keytool / jarsigner(RSA 2048 自签名证书,没有时间戳服务器),BouncyCastle bcpg 1.78.1(RSA 2048、SHA-256 分离签名);本机没有安装 gpg,PGP 部分用 bcpg 的 API 实现,没有 Web of Trust 与密钥服务器;签名与证书的大小每次运行会有几个字节的差别
    • 没有验证:受信任 CA 签发的证书与时间戳服务(TSA)、Maven Central 的发布流程(Sonatype 对 .asc / 校验和的要求)、sigstore / cosign、Gradle / Maven 的签名插件配置、SLSA 来源证明与 SBOM、证书过期后已签名 jar 的行为、Java 17 以上已移除的 SecurityManager 相关策略
    京ICP备2026067502号-1京公网安备11010502063487号