如何使用Java iText库获取多签名PDF中各签名的吊销信息
Java环境基于iText提取多签名PDF各签名吊销信息实现方案
前置依赖准备
使用iText 7.x版本实现(iText 5已停止维护,签名相关API对PAdES标准、DSS存储的支持存在缺失),需要引入的核心依赖包:
itext7-core:iText核心能力包,包含PDF解析、签名处理模块bouncycastle-jdk15on、bouncycastle-pkix:提供PKI体系下CRL、OCSP、证书解析的底层支撑
代码运行前必须先注册BouncyCastle安全提供者,否则会出现安全算法找不到的异常:
Security.addProvider(new BouncyCastleProvider());
分步实现逻辑
1. 加载PDF并提取所有签名字段
已签署PDF的所有签名都存储在AcroForm的签名字段中,既包含用户添加的批准签名,也包含文档级的认证签名,遍历的时候不要做字段过滤遗漏:
// 加载目标PDF文档 PdfDocument pdfDoc = new PdfDocument(new PdfReader("target_signed.pdf")); PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDoc, false); Map<String, PdfFormField> formFields = acroForm.getFormFields();
遍历所有字段,过滤出签名字段后逐个处理:
for (PdfFormField field : formFields.values()) { if (!field.isSignatureField()) { continue; } PdfSignature signature = field.getValueAsSignature(); PdfPKCS7 pkcs7 = signature.getSignatureContainer().getPdfPKCS7(); X509Certificate signCert = (X509Certificate) pkcs7.getSigningCertificate(); System.out.printf("=== 处理签名者:%s 的吊销信息 ===%n", signCert.getSubjectDN().getName()); // 后续提取吊销信息的逻辑写在这里 }
2. 提取签名容器内嵌入的吊销信息
签名生成时,签名工具通常会把对应的CRL(证书吊销列表)、OCSP(在线证书状态查询)响应直接嵌入PKCS7签名容器中,这部分数据可以直接通过iText封装的PdfPKCS7对象获取:
// 提取嵌入的CRL列表 Collection<CRL> embeddedCrls = pkcs7.getCRLs(); for (CRL crl : embeddedCrls) { X509CRL x509Crl = (X509CRL) crl; Set<? extends X509CRLEntry> revokedEntries = x509Crl.getRevokedCertificates(); System.out.printf("签名容器内CRL:颁发者=%s,更新时间=%s,吊销条目数=%d%n", x509Crl.getIssuerDN().getName(), x509Crl.getThisUpdate(), revokedEntries == null ? 0 : revokedEntries.size() ); } // 提取嵌入的OCSP响应 Collection<byte[]> embeddedOcspBytes = pkcs7.getOcspResponses(); for (byte[] ocspRespByte : embeddedOcspBytes) { OCSPResp ocspResp = new OCSPResp(ocspRespByte); BasicOCSPResp basicResp = (BasicOCSPResp) ocspResp.getResponseObject(); System.out.printf("签名容器内OCSP:响应生成时间=%s,状态条目数=%d%n", basicResp.getProducedAt(), basicResp.getResponses().length ); }
3. 提取文档DSS存储区的吊销信息
符合PAdES标准的签名会把吊销信息统一存到PDF根节点的DSS(文档安全存储)字典中,按签名的哈希值建立VRI(签名验证相关信息)索引,这部分数据不会存在PKCS7容器里,必须单独读取:
PdfDictionary catalogDict = pdfDoc.getCatalog().getPdfObject(); PdfDictionary dssDict = catalogDict.getAsDictionary(PdfName.DSS); if (dssDict != null) { PdfDictionary vriRoot = dssDict.getAsDictionary(PdfName.VRI); if (vriRoot != null) { // 计算当前签名内容的SHA1哈希,作为VRI条目的查找键 byte[] sigContent = signature.getContents().getValueBytes(); byte[] sigHash = DigestAlgorithms.digest(sigContent, DigestAlgorithm.SHA1); String vriKey = Hex.toHexString(sigHash).toUpperCase(); PdfDictionary sigVri = vriRoot.getAsDictionary(new PdfName(vriKey)); if (sigVri != null) { // 读取VRI中存储的CRL PdfArray crlArr = sigVri.getAsArray(PdfName.CRL); if (crlArr != null) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); for (int i = 0; i < crlArr.size(); i++) { PdfStream crlStream = crlArr.getAsStream(i); X509CRL vriCrl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(crlStream.getBytes())); System.out.printf("DSS存储CRL:颁发者=%s,更新时间=%s%n", vriCrl.getIssuerDN().getName(), vriCrl.getThisUpdate()); } } // 读取VRI中存储的OCSP响应 PdfArray ocspArr = sigVri.getAsArray(PdfName.OCSP); if (ocspArr != null) { for (int i = 0; i < ocspArr.size(); i++) { PdfStream ocspStream = ocspArr.getAsStream(i); OCSPResp vriOcsp = new OCSPResp(ocspStream.getBytes()); BasicOCSPResp vriBasicOcsp = (BasicOCSPResp) vriOcsp.getResponseObject(); System.out.printf("DSS存储OCSP:响应生成时间=%s%n", vriBasicOcsp.getProducedAt()); } } } } }
注意事项
- 不要尝试手动解析ASN.1结构提取数据:iText已经对不同版本、不同标准的签名结构做了兼容,手动解析很容易出现不同签名工具生成的PDF适配问题。
- 吊销信息可能为空:如果签名生成时没有嵌入CRL/OCSP数据,也没有写入DSS存储区,提取结果就是空集合,这属于签名本身的配置问题,不是代码逻辑错误。
- 多签名场景下每个签名的VRI是独立存储的,后添加的签名不会修改之前签名对应的吊销信息,遍历处理时不需要做额外的签名覆盖范围判断。
- 处理完文档后记得关闭
PdfDocument释放文件句柄。
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

