iText Java v8.0.3远程PKCS7签名PDF遇篡改报错求解决方案
问题:iText 8.0.3 两阶段PDF签名触发“文档已被篡改”提示
场景描述
使用iText Java v8.0.3试用版实现PDF两阶段签名流程:
- 先生成文档哈希,发送至签名服务器获取PKCS7签名
- 将PKCS7签名应用到原PDF文档
遇到的问题:
- 官方文档提及的
PdfPadesSigner类未包含prepareDocumentForSignature()和addSignatureToPreparedDocument()方法 - 尝试v5.5.13.3版本的
PdfPKCS7.getAuthenticatedAttributeBytes方法时,出现签名验证错误,且无签名证书可传入构造器 - 使用
PdfTwoPhaseSigner实现后,最终签名验证提示**"The document has been altered or corrupted since the Signature was applied"**
当前实现代码
步骤1:生成文档哈希
pdfTwoPhaseSigner = new PdfTwoPhaseSigner(new PdfReader(inputStream), new FileOutputStream(tmpFileName)); SignerProperties signerProperties = new SignerProperties(); signerProperties.setPageRect(new Rectangle(36, 748, 144, 780)); signerProperties.setFieldName("swa"); signerProperties.setReason("test reason"); signerProperties.setLocation("test location"); SignatureFieldAppearance signatureFieldAppearance = new SignatureFieldAppearance("swa"); signatureFieldAppearance.setContent("Test Name", "Test description"); signerProperties.setSignatureAppearance(signatureFieldAppearance); byte[] digestedDocumentBytes = pdfTwoPhaseSigner.prepareDocumentForSignature(signerProperties, "SHA256", PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached, (8192 * 2 + 2), true); // ETSI_CAdES_DETACHED return digestedDocumentBytes;
步骤2:远程签名
调用签名服务器接口,返回byte[] pkcs7(完整PKCS7签名数据)和X509Certificate[] chain(证书链)。
步骤3:应用签名到PDF
CMSContainer cms = new CMSContainer(); SignerInfo signerInfo = new SignerInfo(); signerInfo.setSigningCertificate(chain[0]); signerInfo.setSignature(pkcs7); signerInfo.setDigestAlgorithm(new AlgorithmIdentifier(DigestAlgorithms.getAllowedDigest("SHA256"))); cms.addCertificates(chain); cms.setSignerInfo(signerInfo); outputStream = new FileOutputStream(outputFile); try (PdfDocument document = new PdfDocument(new PdfReader(new FileInputStream(tmpFileName)))) { PdfTwoPhaseSigner.addSignatureToPreparedDocument(document, "swa", outputStream, cms); } finally { outputStream.close(); }
解决方案
核心问题
手动构建SignerInfo时错误地将完整PKCS7数据作为签名值传入,导致签名结构不合法,触发篡改检测。远程返回的pkcs7是完整的CMS签名容器,包含所有必要的签名属性,无需手动拆分组装。
修正后的实现代码
步骤1:生成文档哈希(保持原逻辑不变)
PdfTwoPhaseSigner pdfTwoPhaseSigner = new PdfTwoPhaseSigner(new PdfReader(inputStream), new FileOutputStream(tmpFileName)); SignerProperties signerProperties = new SignerProperties(); signerProperties.setPageRect(new Rectangle(36, 748, 144, 780)); signerProperties.setFieldName("swa"); signerProperties.setReason("test reason"); signerProperties.setLocation("test location"); SignatureFieldAppearance signatureFieldAppearance = new SignatureFieldAppearance("swa"); signatureFieldAppearance.setContent("Test Name", "Test description"); signerProperties.setSignatureAppearance(signatureFieldAppearance); byte[] digestedDocumentBytes = pdfTwoPhaseSigner.prepareDocumentForSignature( signerProperties, "SHA256", PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached, 8192 * 2 + 2, true ); return digestedDocumentBytes;
步骤3:修正签名应用逻辑
直接使用远程返回的PKCS7字节数组构建CMSContainer:
// 替换为远程签名返回的实际数据 byte[] pkcs7Bytes = ...; X509Certificate[] chain = ...; // 直接从完整PKCS7数据解析CMSContainer CMSContainer cms = new CMSContainer(pkcs7Bytes); try (FileOutputStream outputStream = new FileOutputStream(outputFile); PdfDocument document = new PdfDocument(new PdfReader(new FileInputStream(tmpFileName)))) { PdfTwoPhaseSigner.addSignatureToPreparedDocument(document, "swa", outputStream, cms); }
关键注意事项
- CMSContainer构建:远程返回的PKCS7是完整签名结构,必须用
new CMSContainer(pkcs7Bytes)直接解析,禁止手动拆分组装SignerInfo。 - 临时文件一致性:步骤1生成的临时文件必须与步骤3使用的完全一致,中途不能修改或覆盖。
- 签名格式匹配:步骤1中指定的签名子过滤器(
PdfName.Adbe_pkcs7_detached)必须与签名服务器生成的PKCS7格式匹配,若服务器生成CAdES格式,需改为PdfName.ETSI_CAdES_DETACHED。
内容的提问来源于stack exchange,提问作者Swaran
相关产品推荐
相关产品推荐

