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

基于BouncyCastle构建X.509证书链实现PDF有效签名

如何构建证书链实现PDF有效数字签名

之前用自签名证书创建KeyStore并签名PDF,PDF能正常生成但Acrobat Reader验证时提示签名有效性未知。现在已购买包含CA证书的合法SSL证书,得构建正确的证书链来实现可验证的有效签名。

之前的创建KeyStore代码片段:

keyStoreFile.createNewFile();

// keys
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
kpGen.initialize(2048, new SecureRandom());
KeyPair pair = kpGen.generateKeyPair();

// certificate
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
certGen.setIssuerDN(new X500Principal("CN=xxx"));
certGen.setNotBefore(new Date(System.currentTimeMillis() - 10000));
certGen.setNotAfter(Date.from(LocalDate.now().plus(365, ChronoUnit.DAYS).atStartOfDay().toInstant(ZoneOffset.UTC)));
certGen.setSubjectDN(new X500Principal("CN=xxx"));
certGen.setPublicKey(pair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");

// extensions
certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth));
certGen.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(new GeneralName(GeneralName.rfc822Name, customer.getEmail())));

X509Certificate cert = certGen.generateX509Certificate(pair.getPrivate(), "BC");

// keystore
KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
ks.load(null,null);
ks.setCertificateEntry(customer.getEmail(), cert);
ks.setKeyEntry(" xxx ", pair.getPrivate(), password, new Certificate[]{cert,cert});
FileOutputStream fos = new FileOutputStream( contractorKeyStorFilePath );
ks.store(fos, password);
fos.close();

问题根源

自签名证书没有受信任的CA背书,Acrobat找不到能验证它的信任链,所以会提示有效性未知。要解决这个问题,得用CA颁发的合法证书(带完整信任链)来创建KeyStore,而不是自己生成自签名证书。

具体解决步骤

1. 准备完整证书链文件

你买的SSL证书应该包含这些文件(不同CA提供商可能命名不同):

  • 你的用户证书:对应你身份/域名的证书,带你的公钥
  • 中级CA证书:签发你用户证书的CA证书
  • 根CA证书:信任链顶端的根证书(一般Acrobat等软件已经预装,也可以手动导入)

确保这些证书是PEM或DER格式,如果是PKCS#7格式,得先提取出单个证书。

2. 拿到证书对应的私钥

申请SSL证书时,你肯定生成了CSR(证书签名请求),同时要保留对应的RSA私钥文件(通常是PEM格式,开头是-----BEGIN PRIVATE KEY-----)。如果私钥是加密的,先用openssl解密:

openssl rsa -in encrypted.key -out decrypted.key

3. 构建带完整证书链的PKCS#12 KeyStore

两种方式可选,推荐用命令行更简单:

方式一:OpenSSL命令行创建(推荐)

openssl pkcs12 -export -out your_keystore.p12 -inkey your_private.key -in your_certificate.crt -certfile intermediate_ca.crt -name "your_alias"
  • -inkey:你的私钥文件路径
  • -in:你的用户证书文件路径
  • -certfile:中级CA证书路径(多个中级CA的话,把它们拼接成一个文件再传入)
  • -name:KeyStore里的别名,后续签名要用
  • 执行后会提示设密码,这个密码就是KeyStore的访问密码

方式二:Java代码构建

如果必须用代码实现,参考下面的示例:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.List;

public class KeyStoreBuilder {
    public static void main(String[] args) throws Exception {
        String privateKeyPath = "path/to/your_private.key";
        String userCertPath = "path/to/your_certificate.crt";
        String intermediateCaPath = "path/to/intermediate_ca.crt";
        String keystorePath = "path/to/your_keystore.p12";
        char[] password = "your_keystore_password".toCharArray();
        String alias = "your_alias";

        // 加载私钥(示例用BouncyCastle解析PEM格式,若私钥加密需额外处理)
        PrivateKey privateKey = loadPrivateKey(privateKeyPath);

        // 加载用户证书
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        Certificate userCert = cf.generateCertificate(new FileInputStream(userCertPath));

        // 加载CA证书链
        List<Certificate> caChain = new ArrayList<>();
        caChain.add(cf.generateCertificate(new FileInputStream(intermediateCaPath)));
        // 多个中级CA的话继续添加
        // caChain.add(cf.generateCertificate(new FileInputStream(another_intermediate.crt)));
        // 根CA可选,因为Acrobat通常已信任根CA
        // caChain.add(cf.generateCertificate(new FileInputStream(root_ca.crt)));

        // 组装完整证书链:用户证书在前,CA链在后
        Certificate[] fullChain = new Certificate[caChain.size() + 1];
        fullChain[0] = userCert;
        System.arraycopy(caChain.toArray(), 0, fullChain, 1, caChain.size());

        // 创建PKCS12 KeyStore
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(null, null);
        ks.setKeyEntry(alias, privateKey, password, fullChain);

        // 保存KeyStore
        try (FileOutputStream fos = new FileOutputStream(keystorePath)) {
            ks.store(fos, password);
        }
    }

    private static PrivateKey loadPrivateKey(String filePath) throws Exception {
        try (FileInputStream fis = new FileInputStream(filePath)) {
            org.bouncycastle.openssl.PEMParser parser = new org.bouncycastle.openssl.PEMParser(new java.io.InputStreamReader(fis));
            Object obj = parser.readObject();
            if (obj instanceof org.bouncycastle.openssl.PEMKeyPair) {
                org.bouncycastle.openssl.PEMKeyPair pemKeyPair = (org.bouncycastle.openssl.PEMKeyPair) obj;
                org.bouncycastle.asn1.pkcs.PrivateKeyInfo privateKeyInfo = pemKeyPair.getPrivateKeyInfo();
                return org.bouncycastle.jce.provider.BouncyCastleProvider.getPrivateKey(privateKeyInfo);
            } else if (obj instanceof org.bouncycastle.asn1.pkcs.PrivateKeyInfo) {
                return org.bouncycastle.jce.provider.BouncyCastleProvider.getPrivateKey((org.bouncycastle.asn1.pkcs.PrivateKeyInfo) obj);
            } else {
                throw new IllegalArgumentException("无效的私钥文件");
            }
        }
    }
}

4. 用新KeyStore签名PDF

签名时确保使用KeyStore里的完整证书链,这样Acrobat就能沿着链验证到受信任的根CA,认可签名有效性。

重要注意事项

  • 证书的Extended Key Usage必须包含id_kp_codeSigning或id_kp_emailProtection(PDF签名需要的用途,你之前的自签名证书用了id_kp_serverAuth,这是给SSL服务器用的,不适合PDF签名,得确认你买的SSL证书是否包含文档/代码签名用途,没有的话可能得重新申请)
  • 根CA证书要在Acrobat的信任列表里,大部分公共CA的根证书已经预装,没预装的话手动导入就行
  • 证书链顺序不能错:用户证书 → 中级CA证书 → 根CA证书(根CA可选)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 13:47:06