You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 10:05:15