如何在Java中读取并验证EXE和DLL的数字签名
在Java中验证EXE/DLL数字证书的方案
以下是无需调用signtool,基于Java原生API或第三方库的实现方案:
方案一:通过JNA调用Windows Crypt API(推荐,适配Windows平台)
Windows系统提供的Crypt32.dll等原生API可直接处理PE文件(EXE/DLL)的数字签名,通过JNA调用这些API能高效提取并验证证书,无需依赖外部工具。
步骤:
引入JNA依赖
在Maven或Gradle中添加JNA及JNA Platform依赖:<!-- Maven --> <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.13.0</version> </dependency> <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna-platform</artifactId> <version>5.13.0</version> </dependency>映射Windows Crypt API接口
定义JNA接口封装所需系统函数:import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.WString; import com.sun.jna.platform.win32.WinCrypt; import com.sun.jna.ptr.PointerByReference; public interface Crypt32 extends Library { Crypt32 INSTANCE = Native.load("Crypt32", Crypt32.class); boolean CryptQueryObject(int dwObjectType, WString pvObject, int dwExpectedContentTypeFlags, int dwFormatTypeFlags, int dwFlags, PointerByReference pdwMsgAndCertEncodingType, PointerByReference pdwContentType, PointerByReference pdwFormatType, PointerByReference phCertStore, PointerByReference phMsg, PointerByReference ppvContext); boolean CryptMsgGetParam(Pointer hMsg, int dwParamType, int dwIndex, Pointer pvData, int[] pcbData); }提取并验证证书
编写代码调用API获取证书链,并用Java原生Security API验证有效性:import com.sun.jna.ptr.PointerByReference; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.List; public class PESignatureVerifier { public static List<X509Certificate> getPECertificates(String peFilePath) throws Exception { List<X509Certificate> certificates = new ArrayList<>(); PointerByReference pdwMsgAndCertEncodingType = new PointerByReference(); PointerByReference pdwContentType = new PointerByReference(); PointerByReference pdwFormatType = new PointerByReference(); PointerByReference phCertStore = new PointerByReference(); PointerByReference phMsg = new PointerByReference(); PointerByReference ppvContext = new PointerByReference(); // 获取PE文件签名信息 boolean success = Crypt32.INSTANCE.CryptQueryObject( WinCrypt.CRYPT_QUERY_OBJECT_FILE, new com.sun.jna.WString(peFilePath), WinCrypt.CRYPT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, WinCrypt.CRYPT_QUERY_FORMAT_FLAG_BINARY, 0, pdwMsgAndCertEncodingType, pdwContentType, pdwFormatType, phCertStore, phMsg, ppvContext ); if (!success) { throw new Exception("Failed to query PE signature"); } // 提取证书链 int[] pcbData = new int[1]; Crypt32.INSTANCE.CryptMsgGetParam(phMsg.getValue(), WinCrypt.CMSG_CERT_COUNT_PARAM, 0, null, pcbData); int certCount = pcbData[0]; CertificateFactory cf = CertificateFactory.getInstance("X.509"); for (int i = 0; i < certCount; i++) { Crypt32.INSTANCE.CryptMsgGetParam(phMsg.getValue(), WinCrypt.CMSG_CERT_PARAM, i, null, pcbData); byte[] certBytes = new byte[pcbData[0]]; Crypt32.INSTANCE.CryptMsgGetParam(phMsg.getValue(), WinCrypt.CMSG_CERT_PARAM, i, new com.sun.jna.Memory(pcbData[0]).write(0, certBytes, 0, pcbData[0]), pcbData); X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certBytes)); certificates.add(cert); } // 验证证书有效性(有效期、信任链等) for (X509Certificate cert : certificates) { cert.checkValidity(); // 可扩展:验证证书是否在系统信任存储中,需读取Windows根证书到Java KeyStore } return certificates; } public static void main(String[] args) throws Exception { List<X509Certificate> certs = getPECertificates("C:\\path\\to\\target.exe"); for (X509Certificate cert : certs) { System.out.println("证书主题: " + cert.getSubjectDN()); System.out.println("证书颁发者: " + cert.getIssuerDN()); System.out.println("有效期至: " + cert.getNotAfter()); } } }
方案二:纯Java解析PE文件+ BouncyCastle验证证书
若需跨平台处理PE文件,可通过纯Java解析PE结构提取PKCS#7签名数据,再用BouncyCastle解析验证证书。
步骤:
引入BouncyCastle依赖
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency>解析PE文件签名并验证证书
import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.io.FileInputStream; import java.security.Security; import java.security.cert.X509Certificate; import java.util.Collection; public class PEParserVerifier { static { Security.addProvider(new BouncyCastleProvider()); } public static Collection<X509Certificate> getPECertificates(String peFilePath) throws Exception { // 读取PE文件中的PKCS#7签名数据 byte[] signatureData = readPESignatureData(peFilePath); // 解析签名数据获取证书链 CMSSignedData cmsSignedData = new CMSSignedData(signatureData); return (Collection<X509Certificate>) cmsSignedData.getCertificates().getMatches(null); } private static byte[] readPESignatureData(String peFilePath) throws Exception { try (FileInputStream fis = new FileInputStream(peFilePath)) { // 跳过DOS头,读取NT头偏移 fis.skip(0x3C); int ntHeaderOffset = readInt(fis); // 跳转到数据目录表的安全条目 fis.skip(ntHeaderOffset + 0x78); int securityDirVirtualAddress = readInt(fis); int securityDirSize = readInt(fis); if (securityDirSize == 0) { throw new Exception("PE file is not signed"); } // 读取签名数据 fis.skip(securityDirVirtualAddress); byte[] signatureData = new byte[securityDirSize]; fis.read(signatureData); return signatureData; } } private static int readInt(FileInputStream fis) throws Exception { byte[] bytes = new byte[4]; fis.read(bytes); return ((bytes[3] & 0xFF) << 24) | ((bytes[2] & 0xFF) << 16) | ((bytes[1] & 0xFF) << 8) | (bytes[0] & 0xFF); } public static void main(String[] args) throws Exception { Collection<X509Certificate> certs = getPECertificates("C:\\path\\to\\target.dll"); for (X509Certificate cert : certs) { System.out.println("证书主题: " + cert.getSubjectDN()); cert.checkValidity(); // 验证证书有效期 } } }
注意事项:
- 方案一依赖Windows Crypt API,会自动校验PE文件签名的完整性(确保文件未被篡改),是最可靠的实现方式。
- 方案二的PE解析逻辑需完善边界处理(如适配32/64位PE文件),且需自行实现签名与文件内容的一致性校验。
- 证书验证需覆盖:有效期检查、信任链验证(需加载Windows系统根证书到Java KeyStore)、签名完整性校验。
内容的提问来源于stack exchange,提问作者Naveen S
相关产品推荐
相关产品推荐

