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