基于Java读取JAR文件代码签名与证书信息的技术方案问询
嘿,我太懂你不想解析jarsigner输出的麻烦了——那玩意儿的输出格式偶尔会变,写解析逻辑很容易踩坑。其实Java本身就提供了一套原生API,能直接读取JAR的签名、证书和时间戳信息,完全不用依赖外部命令,比你之前的方案靠谱多了!
下面我就一步步教你怎么实现所有需求:
用Java原生API处理JAR签名信息
核心用到的是java.util.jar、java.security和javax.security.auth.x500包里的类,不用额外引入依赖,JDK自带就能用。
1. 检查JAR是否已签名 + 验证签名完整性
当你用JarFile打开JAR文件时,JVM会自动验证签名的完整性(只要你不关闭验证)。我们可以通过获取JarEntry的CodeSigner对象来判断是否已签名,同时验证签名是否未被篡改。
示例代码片段:
import java.io.File; import java.util.jar.JarFile; import java.util.jar.JarEntry; import java.security.CodeSigner; public boolean isJarSignedAndValid(File jarFile) throws Exception { try (JarFile jar = new JarFile(jarFile, true)) { // 第二个参数true表示开启签名验证 // 遍历JAR里的非签名文件条目(跳过META-INF下的.SF/.RSA/.DSA等签名文件) for (JarEntry entry : jar.stream().toList()) { if (entry.isDirectory() || entry.getName().startsWith("META-INF/")) { continue; } // 尝试获取条目签名信息 CodeSigner[] signers = entry.getCodeSigners(); if (signers != null && signers.length > 0) { // 如果有签名,说明JAR已签名,且JVM已经验证过签名完整性(没抛异常就是有效) return true; } } // 遍历完所有条目都没找到签名信息,说明未签名 return false; } catch (Exception e) { // 验证过程中抛出异常,说明签名无效(比如文件被篡改) System.err.println("签名验证失败:" + e.getMessage()); return false; } }
注意:如果JAR被篡改,
JarFile在读取条目时会直接抛出SecurityException,这就是签名无效的信号。
2. 检查是否使用TSA时间戳
每个CodeSigner对象都包含时间戳信息,只要调用getTimestamp()方法,返回非null就说明签名时用了TSA:
import java.security.Timestamp; // 接上面的代码,当找到CodeSigner时 for (CodeSigner signer : signers) { Timestamp timestamp = signer.getTimestamp(); if (timestamp != null) { System.out.println("使用了TSA时间戳,时间:" + timestamp.getTimestamp()); // 还能获取时间戳的证书信息 System.out.println("TSA证书CN:" + timestamp.getSignerCertPath().getCertificates().get(0).getSubjectX500Principal().getName()); } else { System.out.println("未使用TSA时间戳"); } }
3. 获取签名证书的CN、O等字段
从CodeSigner里拿到签名证书链,取第一个证书(就是直接签名的那个证书),然后解析其X.500主体名称:
import java.security.cert.X509Certificate; import javax.security.auth.x500.X500Principal; // 接上面的CodeSigner遍历 X509Certificate signerCert = (X509Certificate) signer.getSignerCertPath().getCertificates().get(0); X500Principal subject = signerCert.getSubjectX500Principal(); // 解析CN(通用名称)、O(组织名称)等字段 String dn = subject.getName(X500Principal.RFC2253); // 拆分DN字段,比如:CN=John Doe, O=Example Corp, L=New York String[] dnParts = dn.split(", "); for (String part : dnParts) { if (part.startsWith("CN=")) { System.out.println("证书CN:" + part.substring(3)); } else if (part.startsWith("O=")) { System.out.println("证书组织O:" + part.substring(2)); } }
4. 获取证书过期时间 + 签名相关时间
- 证书的过期时间直接从
X509Certificate的getNotAfter()方法获取:
System.out.println("证书过期时间:" + signerCert.getNotAfter());
- 如果有TSA时间戳,时间戳的时间就是
timestamp.getTimestamp(),这个可以作为签名的可信时间(比证书生效时间更准确)。
为什么这个方案更优?
- 完全原生:不用调用外部命令,避免了跨平台兼容性问题(比如Windows和Linux的
jarsigner输出格式差异) - 更灵活:可以自定义验证逻辑,比如只验证特定证书的签名,或者批量处理多个JAR
- 更可靠:JVM原生的签名验证逻辑比自己解析输出要严谨得多,不容易出错
内容的提问来源于stack exchange,提问作者PaulEdison
相关产品推荐
相关产品推荐

