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

如何在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);
    }
}

错误情况

  1. 运行上述代码时抛出错误:com.itextpdf.kernel.exceptions.PdfException: Unknown PdfException.
  2. 将代码中:
    PdfPKCS7 sgn = new PdfPKCS7(null, chain[0].getEncoded(), null);
    return sgn.getEncodedPKCS7(p7sFile, PdfSigner.CryptoStandard.CMS, null, null, null);
    
    替换为:
    return p7sFile;
    
    生成了已签名PDF,但API验证器报错:

    Attributes -> Required Attributes -> IdMessageDigest -> 未通过。构造属性失败,获取哈希时出现问题
    IdContentType -> 通过

解决方法

  1. 保留代码修改:直接返回p7sFile,不再用PdfPKCS7重新编码
  2. 修正哈希计算逻辑:此前是对带空白签名的临时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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 07:44:55