如何修改iTextPDF签名代码让签名区域显示签名人姓名
问题
使用iTextPDF工具包完成PDF签名后,签名区域仅显示“Digitally signed by...”,未展示签名人姓名,但PDF查看工具已识别到签名人信息。请修改以下代码,使签名区域显示签名人信息:
static byte[] prepareAndHashForSigning(PdfReader pdfReader, OutputStream outputStream, String fieldName) throws DocumentException, IOException, GeneralSecurityException { PdfStamper pdfStamper = PdfStamper.createSignature(pdfReader, outputStream, (char) 0); PdfSignatureAppearance pdfSignatureAppearance = pdfStamper.getSignatureAppearance(); pdfSignatureAppearance.setVisibleSignature(new Rectangle(80, 140, 188, 172), 1, fieldName); ExternalSignatureContainer blankContainer = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED); MakeSignature.signExternalContainer(pdfSignatureAppearance, blankContainer, 12000); InputStream rangeStream = pdfSignatureAppearance.getRangeStream(); BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest(); byte[] hash = DigestAlgorithms.digest(rangeStream, bouncyCastleDigest.getMessageDigest("SHA256")); return hash; } /** * This method sets the signature container placeholder in the signature dictionary * value of the signature field with the given name. */ static void injectPkcs7Container(PdfReader pdfReader, OutputStream outputStream, String fieldName, byte[] signatureContainer) throws DocumentException, IOException, GeneralSecurityException { ExternalSignatureContainer injectingContainer = new InjectingSignatureContainer(signatureContainer); MakeSignature.signDeferred(pdfReader, fieldName, outputStream, injectingContainer); } /** * This is a dummy method that returns the given hash itself instead of a signature * container for it. Obviously, it is not for production purposes. */ static byte[] retrievePkcs7ContainerForHash(byte[] hash) { String base64CertificateString ="....."; byte[] decodedCertificateBytes = Base64.getDecoder().decode(base64CertificateString); return decodedCertificateBytes; } /** * This {@link ExternalSignatureContainer} implementation returns a pre-generated * byte array in its {@link #sign(InputStream)} method. */ static class InjectingSignatureContainer implements ExternalSignatureContainer { final byte[] signatureContainer; public InjectingSignatureContainer(byte[] signatureContainer) { this.signatureContainer = signatureContainer; } @Override public byte[] sign(InputStream data) throws GeneralSecurityException { return signatureContainer; } @Override public void modifySigningDictionary(PdfDictionary signDic) { } } public static void main(String[] args) throws IOException { File origFile = new File("POL571_RedLight_company.pdf"); File tempFile = new File("deferredContainerSigningTemp.pdf"); File resultFile = new File("deferredContainerSigningResult.pdf"); String fieldName = "Signature"; byte[] hash = null; try ( OutputStream tempOutput = Files.newOutputStream(tempFile.toPath()) ) { PdfReader pdfReader = new PdfReader(origFile.getPath()); hash = prepareAndHashForSigning(pdfReader, tempOutput, fieldName); String s = Base64.getEncoder().encodeToString(hash); System.out.println(s); } catch (DocumentException | GeneralSecurityException | IOException e) { throw new RuntimeException(e); } byte[] signatureContainer = retrievePkcs7ContainerForHash(hash); try ( OutputStream resultOutput = Files.newOutputStream(resultFile.toPath()) ) { PdfReader pdfReader = new PdfReader(tempFile.getPath()); injectPkcs7Container(pdfReader, resultOutput, fieldName, signatureContainer); } catch (DocumentException | GeneralSecurityException e) { throw new RuntimeException(e); } }
解决方案
要让签名区域显示签名人姓名,需从签名外观配置和合法PKCS7容器生成两方面调整代码,以下是具体修改:
1. 在签名外观中设置签名人姓名
修改prepareAndHashForSigning方法,加载证书并提取签名人姓名,通过setSignerName方法绑定到签名外观:
static byte[] prepareAndHashForSigning(PdfReader pdfReader, OutputStream outputStream, String fieldName, X509Certificate cert) throws DocumentException, IOException, GeneralSecurityException { PdfStamper pdfStamper = PdfStamper.createSignature(pdfReader, outputStream, (char) 0); PdfSignatureAppearance pdfSignatureAppearance = pdfStamper.getSignatureAppearance(); // 从证书提取签名人姓名(解析CN字段) String subjectDN = cert.getSubjectX500Principal().getName(X500Principal.RFC2253); String signerName = subjectDN.split(",")[0].replace("CN=", ""); pdfSignatureAppearance.setSignerName(signerName); pdfSignatureAppearance.setVisibleSignature(new Rectangle(80, 140, 188, 172), 1, fieldName); ExternalSignatureContainer blankContainer = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED); MakeSignature.signExternalContainer(pdfSignatureAppearance, blankContainer, 12000); InputStream rangeStream = pdfSignatureAppearance.getRangeStream(); BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest(); byte[] hash = DigestAlgorithms.digest(rangeStream, bouncyCastleDigest.getMessageDigest("SHA256")); return hash; }
2. 修正PKCS7签名容器生成逻辑
原代码中retrievePkcs7ContainerForHash仅返回证书字节,不是合法的签名容器。需用私钥对哈希签名,生成完整的PKCS7结构:
static byte[] retrievePkcs7ContainerForHash(byte[] hash, PrivateKey privateKey, X509Certificate cert) throws GeneralSecurityException { CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); // 创建签名器 ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey); // 添加签名信息生成器 generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer, cert)); // 添加证书到容器 generator.addCertificate(new JcaX509CertificateHolder(cert)); // 生成签名容器 CMSProcessableByteArray data = new CMSProcessableByteArray(hash); CMSSignedData signedData = generator.generate(data, false); return signedData.getEncoded(); }
3. 调整主方法参数传递
在main方法中加载证书和私钥,传递给对应方法:
public static void main(String[] args) throws IOException, GeneralSecurityException, DocumentException { File origFile = new File("POL571_RedLight_company.pdf"); File tempFile = new File("deferredContainerSigningTemp.pdf"); File resultFile = new File("deferredContainerSigningResult.pdf"); String fieldName = "Signature"; // 加载证书(替换为你的证书Base64字符串) String base64Cert = "....."; CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(base64Cert))); // 加载私钥(替换为你的私钥Base64字符串,需是PKCS8格式) String base64PrivateKey = "....."; KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64PrivateKey))); byte[] hash = null; try (OutputStream tempOutput = Files.newOutputStream(tempFile.toPath())) { PdfReader pdfReader = new PdfReader(origFile.getPath()); hash = prepareAndHashForSigning(pdfReader, tempOutput, fieldName, cert); System.out.println(Base64.getEncoder().encodeToString(hash)); } catch (Exception e) { throw new RuntimeException(e); } byte[] signatureContainer = retrievePkcs7ContainerForHash(hash, privateKey, cert); try (OutputStream resultOutput = Files.newOutputStream(resultFile.toPath())) { PdfReader pdfReader = new PdfReader(tempFile.getPath()); injectPkcs7Container(pdfReader, resultOutput, fieldName, signatureContainer); } catch (Exception e) { throw new RuntimeException(e); } }
可选:补充签名字典信息
在InjectingSignatureContainer中手动设置签名字典的姓名字段,确保签名信息完整性:
static class InjectingSignatureContainer implements ExternalSignatureContainer { final byte[] signatureContainer; final String signerName; public InjectingSignatureContainer(byte[] signatureContainer, String signerName) { this.signatureContainer = signatureContainer; this.signerName = signerName; } @Override public byte[] sign(InputStream data) throws GeneralSecurityException { return signatureContainer; } @Override public void modifySigningDictionary(PdfDictionary signDic) { signDic.put(PdfName.NAME, new PdfString(signerName)); } }
同时修改injectPkcs7Container方法传递签名人姓名:
static void injectPkcs7Container(PdfReader pdfReader, OutputStream outputStream, String fieldName, byte[] signatureContainer, String signerName) throws DocumentException, IOException, GeneralSecurityException { ExternalSignatureContainer injectingContainer = new InjectingSignatureContainer(signatureContainer, signerName); MakeSignature.signDeferred(pdfReader, fieldName, outputStream, injectingContainer); }
内容的提问来源于stack exchange,提问作者NeeDKK
相关产品推荐
相关产品推荐

