基于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
相关产品推荐
相关产品推荐

