如何在Java中为PDF附加API返回的p7s签名?
PDF信封式签名嵌入问题及解决方法
需求步骤
- 准备签名文档
- 计算步骤1生成文件的指定字节范围哈希(PDF信封式签名不对完整文档取SHA256哈希,仅针对部分内容)
- 计算这些字节的SHA256哈希值
- 将该哈希值提交至API的
signPKCS7操作 - 将
signPKCS7结果编码为十六进制,嵌入步骤1预先分配的文档空间内
问题背景
从API获取了.crt证书和.p7s文件,尝试多种方式将p7s签名附加到PDF均失败。对方平台验证步骤1生成的PDF和p7s文件有效,但自行附加后无法通过验证。
尝试的Java代码
private void signature() throws GeneralSecurityException, IOException { pdf = new File(pdfUploadedAddress); pdf.mkdirs(); BouncyCastleProvider providerBC = new BouncyCastleProvider(); Security.addProvider(providerBC); CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC"); Certificate[] chain = new Certificate[1]; File initialFile = new File(pathFileCertificate); InputStream stream = new FileInputStream(initialFile); chain[0] = factory.generateCertificate(stream); emptySignature(pdfUploadedAddress, TEMP, "sig", chain); createSignature(TEMP, DEST + RESULT_FILES[0], "sig", chain); } public void emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, GeneralSecurityException { PdfReader reader = new PdfReader(src); PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties()); PdfSignatureAppearance appearance = signer.getSignatureAppearance(); appearance.setPageRect(new Rectangle(36, 748, 200, 100)).setPageNumber(1).setCertificate(chain[0]); signer.setFieldName(fieldname); /* * ExternalBlankSignatureContainer constructor will create the PdfDictionary for * the signature information and will insert the /Filter and /SubFilter values * into this dictionary. It will leave just a blank placeholder for the * signature that is to be inserted later. */ IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached); // Sign the document using an external container. // 8192 is the size of the empty signature placeholder. signer.signExternalContainer(external, 8192); } public void createSignature(String src, String dest, String fieldName, Certificate[] chain) throws IOException, GeneralSecurityException { PdfReader reader = new PdfReader(src); try (FileOutputStream fos = new FileOutputStream(dest)) { PdfSigner signer = new PdfSigner(reader, fos, new StampingProperties()); IExternalSignatureContainer external = new MyExternalSignatureContainer(chain); signer.signDeferred(signer.getDocument(), fieldName, fos, external); } } class MyExternalSignatureContainer implements IExternalSignatureContainer { protected Certificate[] chain; public MyExternalSignatureContainer(Certificate[] chain) { this.chain = chain; } public byte[] sign(InputStream is) throws GeneralSecurityException { try { File file = new File("src/main/resources/signature.p7s"); byte[] p7sFile = Files.readAllBytes(file.toPath()); PdfPKCS7 sgn = new PdfPKCS7(null, chain[0].getEncoded(), null); return sgn.getEncodedPKCS7(p7sFile, PdfSigner.CryptoStandard.CMS, null, null, null); } catch (IOException ioe) { throw new RuntimeException(ioe); } } public void modifySigningDictionary(PdfDictionary signDic) { signDic.put(PdfName.Filter, PdfName.Adobe_PPKLite); // change here signDic.put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached); } }
错误情况
- 运行上述代码时抛出错误:
com.itextpdf.kernel.exceptions.PdfException: Unknown PdfException. - 将代码中:
替换为:PdfPKCS7 sgn = new PdfPKCS7(null, chain[0].getEncoded(), null); return sgn.getEncodedPKCS7(p7sFile, PdfSigner.CryptoStandard.CMS, null, null, null);
生成了已签名PDF,但API验证器报错:return p7sFile;Attributes -> Required Attributes -> IdMessageDigest -> 未通过。构造属性失败,获取哈希时出现问题
IdContentType -> 通过
解决方法
- 保留代码修改:直接返回
p7sFile,不再用PdfPKCS7重新编码 - 修正哈希计算逻辑:此前是对带空白签名的临时PDF计算哈希,正确做法是对不含签名部分的内容计算哈希。利用iText提供的
sign()方法传入的InputStream,计算已准备PDF的哈希,修改后的sign方法如下:
public byte[] sign(InputStream is) throws GeneralSecurityException { try { BouncyCastleDigest digest = new BouncyCastleDigest(); byte[] hash = DigestAlgorithms.digest(is, digest.getMessageDigest("SHA256")); // 调用API生成.p7s签名 signPKCS7API(hash); File file = new File("src/main/resources/signature.p7s"); byte[] p7sFile = Files.readAllBytes(file.toPath()); return p7sFile; } catch (IOException ioe) { throw new RuntimeException(ioe); } }
内容的提问来源于stack exchange,提问作者ero
相关产品推荐
相关产品推荐

