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

如何使用Java提取已签名.exe文件的数字签名(无需外部工具,指纹即可)

Extracting Digital Signature Fingerprint from Signed .exe with Pure Java

Got it, let's walk through how to pull the digital signature fingerprint from a signed .exe using only Java—no external tools like signtool needed. Here's a practical, code-focused approach:

Core Idea

Signed .exes follow the PE (Portable Executable) format, which tucks the digital signature in a PKCS#7 blob referenced by the PE's security directory entry. Our plan is:

  1. Parse the PE file headers to locate the signature blob offset and size
  2. Extract that PKCS#7 blob from the file
  3. Parse the blob to get the embedded X.509 certificate
  4. Calculate the certificate's fingerprint (SHA-1, SHA-256, etc.)

Step-by-Step Implementation

1. Parse PE Headers to Find Signature Location

PE files start with a DOS header, which points to the NT headers. We need to read these headers to find the IMAGE_DIRECTORY_ENTRY_SECURITY entry, which holds the signature's position and length.

Note: PE uses little-endian byte order, so we'll need helper methods to convert bytes to integers correctly.

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

// Helper to read little-endian integers from PE headers
private static int readLEInt(byte[] bytes, int offset) {
    ByteBuffer buffer = ByteBuffer.wrap(bytes, offset, 4);
    buffer.order(ByteOrder.LITTLE_ENDIAN);
    return buffer.getInt();
}

private static long readLELong(byte[] bytes, int offset) {
    ByteBuffer buffer = ByteBuffer.wrap(bytes, offset, 8);
    buffer.order(ByteOrder.LITTLE_ENDIAN);
    return buffer.getLong();
}

// Locate the signature blob's position and size in the PE file
private static long[] findSignatureBlob(RandomAccessFile raf) throws Exception {
    // Read DOS header to get the offset of the PE header
    byte[] dosHeader = new byte[64];
    raf.readFully(dosHeader);
    int peHeaderOffset = readLEInt(dosHeader, 0x3C);
    
    // Read NT headers to access the optional header's data directory
    raf.seek(peHeaderOffset);
    byte[] ntHeader = new byte[24];
    raf.readFully(ntHeader);
    int sizeOfOptionalHeader = readLEInt(ntHeader, 16);
    
    // Seek to the security directory entry in the data directory table
    int dataDirOffset = peHeaderOffset + 24 + sizeOfOptionalHeader - (16 * 8);
    raf.seek(dataDirOffset);
    byte[] securityDir = new byte[8];
    raf.readFully(securityDir);
    
    long sigOffset = readLELong(securityDir, 0);
    long sigSize = readLELong(securityDir, 4);
    
    // If offset is 0, the file isn't digitally signed
    if (sigOffset == 0) {
        throw new Exception("No digital signature found in the executable");
    }
    
    return new long[]{sigOffset, sigSize};
}

2. Extract PKCS#7 Signature Blob

Once we have the offset and size, we can read the signature blob directly from the file. Note that the first 8 bytes are a WIN_CERTIFICATE header, so we'll skip those to get the raw PKCS#7 data:

private static byte[] extractSignatureBlob(RandomAccessFile raf, long offset, long size) throws Exception {
    raf.seek(offset);
    byte[] blob = new byte[(int) size];
    raf.readFully(blob);
    // Skip the 8-byte WIN_CERTIFICATE header to get the PKCS#7 payload
    return java.util.Arrays.copyOfRange(blob, 8, blob.length);
}

3. Parse PKCS#7 Blob and Get Certificate Fingerprint

Use Java's built-in security APIs to parse the PKCS#7 blob, extract the X.509 certificate, and calculate its fingerprint:

import java.security.MessageDigest;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.io.ByteArrayInputStream;

private static String getCertificateFingerprint(byte[] pkcs7Blob, String algorithm) throws Exception {
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    // PKCS#7 blobs can contain multiple certificates; we'll take the first one (the signer's cert)
    X509Certificate cert = (X509Certificate) cf.generateCertificates(new ByteArrayInputStream(pkcs7Blob)).iterator().next();
    
    MessageDigest md = MessageDigest.getInstance(algorithm);
    byte[] fingerprintBytes = md.digest(cert.getEncoded());
    
    // Convert raw bytes to a human-readable hex string
    StringBuilder sb = new StringBuilder();
    for (byte b : fingerprintBytes) {
        sb.append(String.format("%02X:", b));
    }
    return sb.deleteCharAt(sb.length() - 1).toString(); // Remove the trailing colon
}

4. Putting It All Together

Here's a main method to tie all the pieces together:

public static void main(String[] args) {
    String exePath = "path/to/your/signed.exe";
    String fingerprintAlgorithm = "SHA-256"; // Use "SHA-1" if you need that legacy format
    
    try (RandomAccessFile raf = new RandomAccessFile(exePath, "r")) {
        long[] sigInfo = findSignatureBlob(raf);
        byte[] pkcs7Blob = extractSignatureBlob(raf, sigInfo[0], sigInfo[1]);
        String fingerprint = getCertificateFingerprint(pkcs7Blob, fingerprintAlgorithm);
        
        System.out.printf("%s Fingerprint: %s%n", fingerprintAlgorithm, fingerprint);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Important Notes

  • Little-Endian Handling: PE headers use little-endian byte order, which is why we rely on ByteBuffer with ByteOrder.LITTLE_ENDIAN—don't skip this, or your header parsing will be wrong!
  • Unsigned Files: The code throws an exception if no signature is found; you can adjust this to return a null or empty string if you need to handle unsigned files gracefully.
  • Fingerprint Algorithms: SHA-1 is still widely used but deprecated for many security-critical use cases. SHA-256 is the recommended modern alternative.
  • Memory Efficiency: Using RandomAccessFile means we only load the parts of the .exe we need, not the entire file—great for large executables.

内容的提问来源于stack exchange,提问作者user3413804

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:38:25