You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java中读取并验证EXE和DLL的数字签名

在Java中验证EXE/DLL数字证书的方案

以下是无需调用signtool,基于Java原生API或第三方库的实现方案:

方案一:通过JNA调用Windows Crypt API(推荐,适配Windows平台)

Windows系统提供的Crypt32.dll等原生API可直接处理PE文件(EXE/DLL)的数字签名,通过JNA调用这些API能高效提取并验证证书,无需依赖外部工具。

步骤:

  1. 引入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>
    
  2. 映射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);
    }
    
  3. 提取并验证证书
    编写代码调用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解析验证证书。

步骤:

  1. 引入BouncyCastle依赖

    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk15on</artifactId>
        <version>1.70</version>
    </dependency>
    
  2. 解析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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 13:04:56