当前位置: 首页 > 知识库问答 >
问题:

iText7延迟签名的pdf文档显示“自应用签名以来文档已被更改或损坏”

齐成双
2023-03-14

我检查了Stackoverflow上的其他类似问题,但它在我的案例中不起作用。

情境:我正在开发一个需要签署pdf文档的应用程序。签名密钥由另一家公司持有,假设它是Companya。

我做了以下几个步骤:

    null
public static String getHashBase64Str2Sign() {
    try {
        // Add BC provider
        BouncyCastleProvider providerBC = new BouncyCastleProvider();
        Security.addProvider(providerBC);

        // Create parent path of dest pdf file, if not exist
        File file = new File(DEST).getParentFile();
        if (!file.exists()) {
            file.mkdirs();
        }


        CertificateFactory factory = CertificateFactory.getInstance("X.509");
        Certificate[] chain = new Certificate[1];
        try (InputStream certIs = new FileInputStream(CERT)) {
            chain[0] = factory.generateCertificate(certIs);
        }

        // Get byte[] hash
        DeferredSigning app = new DeferredSigning();

        app.emptySignature(SRC, TEMP, "sig", chain);

        byte[] sh = app.getSignatureHash(TEMP, "SHA256", chain);

        // Encode byte[] hash to base64 String and return
        return Base64.getEncoder().encodeToString(sh);
    } catch (IOException | GeneralSecurityException e) {
        e.printStackTrace();
        return null;
    }
}

    

private 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(100, 500, 200, 100));
    appearance.setPageNumber(1);
    appearance.setCertificate(chain[0]);
    appearance.setReason("For test");
    appearance.setLocation("HKSAR");
    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, 100000);
}

private byte[] getSignatureHash(String src, String hashAlgorithm, Certificate[] chain)
        throws IOException, GeneralSecurityException {
    InputStream is = new FileInputStream(src);
    // Get the hash
    BouncyCastleDigest digest = new BouncyCastleDigest();
    PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);

    byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(sgn.getHashAlgorithm()));
    return sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
}

private void createSignature(String src, String dest, String fieldName, byte[] sig)
        throws IOException, GeneralSecurityException {
    PdfReader reader = new PdfReader(src);
    try (FileOutputStream os = new FileOutputStream(dest)) {
        PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());

        IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);

        // Signs a PDF where space was already reserved. The field must cover the whole
        // document.
        PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
    }
}

然后,将消息摘要发送给CompanyA进行签名。在我从CompanyA获得签名的摘要(它是base64编码的)之后,我调用EmbedsignedHashtopDF()方法来获得签名的pdf文档。

public static void embedSignedHashToPdf(String signedHash) {
    try {
        byte[] sig = Base64.getDecoder().decode(signedHash);
        // Get byte[] hash
        DeferredSigning app = new DeferredSigning();
        app.createSignature(TEMP, DEST, "sig", sig);
    } catch (IOException | GeneralSecurityException e) {
        e.printStackTrace();
    }
}

class MyExternalSignatureContainer implements IExternalSignatureContainer {

    protected byte[] sig;

    public MyExternalSignatureContainer(byte[] sig) {
        this.sig = sig;
    }

    @Override
    public void modifySigningDictionary(PdfDictionary signDic) {

    }

    @Override
    public byte[] sign(InputStream arg0) throws GeneralSecurityException {
        return sig;
    }

}

最后我可以得到签名的pdf文档,但是在Adobe Reader中显示错误,像这样:

请检查原始pdf、临时pdf和最终签名的pdf文件如下:

原始pdf-helloworld.pdf

临时pdf-helloworld_empty_signed.pdf

最终pdf-helloworld_signed_ok.pdf

共有1个答案

严正初
2023-03-14

好的,我在您的代码中看到了一些问题:

getSignatureHashsrc中,包含为签名准备的中间PDF的路径

InputStream is = new FileInputStream(src);
...
byte hash[] = DigestAlgorithms.digest(is, ...);

即。您计算整个中间PDF的哈希值。

例如,使用以下实现:

public class PreSignatureContainer implements IExternalSignatureContainer {
    private PdfDictionary sigDic;
    private byte hash[];

    public PreSignatureContainer(PdfName filter, PdfName subFilter) {
        sigDic = new PdfDictionary();
        sigDic.put(PdfName.Filter, filter);
        sigDic.put(PdfName.SubFilter, subFilter);
    }

    @Override
    public byte[] sign(InputStream data) throws GeneralSecurityException {
        String hashAlgorithm = "SHA256";
        BouncyCastleDigest digest = new BouncyCastleDigest();

        try {
            this.hash = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
        } catch (IOException e) {
            throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
        }

        return new byte[0];
    }

    @Override
    public void modifySigningDictionary(PdfDictionary signDic) {
        signDic.putAll(sigDic);
    }

    public byte[] getHash() {
        return hash;
    }
}

像这样:

PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.signExternalContainer(external, 16000);
byte[] documentHash = external.getHash();

getSignatureHash中,您最终不会返回所谓的文档哈希,而是开始构造CMS签名容器并返回其签名属性:

PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
...
return sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, sigtype, ocspList, crlBytes);
byte[] extSignature = RETRIEVE_NAKED_SIGNATURE_BYTES_FOR(sh);
sgn.setExternalDigest(extSignature, null, ENCRYPTION_ALGORITHM_USED_FOR_SIGNING);

byte[] encodedSig = sgn.getEncodedPKCS7(hash, sigtype, tsaClient, ocspList, crlBytes);

在这种情况下,您根本不需要使用pdfpkcs7,而是直接将预先计算好的文档摘要发送给CompanyA进行签名。

因此,您很可能根本不需要pdfpkcs7,而是将如上所述确定的文档哈希发送给CompanyA,并嵌入返回的签名容器。

 类似资料:
  • 我已经研究了所有类似的问题,但找不到一个应用itextsharp延迟签名的案例。 基本上,我的应用程序使用签名对pdf文档进行签名,该签名是由远程web服务创建的。 我的应用程序向这个web服务发送原始文档的哈希(添加空签名字段后可签名字节的哈希),并接收一个Base64编码的签名文件。 我将此签名嵌入到先前生成的临时pdf文件中,该文件具有空签名字段。 最后,我的签名未被验证,因为Adobe R

  • 我试图对pdf文件进行签名,但在Adobe中打开签名的pdf文件时,遇到“文档自签名后已被更改或损坏”错误。 这个错误不是那么描述性的,我不确定应该在哪里查看,因为代码对我来说似乎很好,但显然不是。。 我使用的代码是: 签名的哈希的Base64格式为(tmp文件sha_前缀): 签名(AMA)的Base64格式为: 有人能帮忙吗?

  • 我正在使用PDFbox-1.8.8在PDF文件上做签名功能。 当我签署一份文件的时候 下面是我的代码:

  • 我正在按代码创建一个签名PDF,但由于某种原因,我收到此错误: 至少一个签名无效-文档签名后已被更改或损坏 我将DSS属性与VRI、Certs和CRL一起使用。重要的一点是,我使用的版本与我在讨论此过程的其他文章中看到的版本相同。所以我有第一部分的签名(内容<>和byterange[])和第二部分与他的孩子的DSS。我注意到,如果删除第二个生成的部分,Adobe Acrobat将给出: 已签名且所

  • 目标是实现一个PDF签名过程,其中服务器根据请求向客户端提供要签名的哈希。然后,客户端使用通过PKCS#11接口从智能卡获得的私钥对给定哈希进行签名。然后,签名被发送回服务器,以便使用iTextSharp 5.5.4附加到PDF文件中。 在Acrobat Reader中查看签名时,我发现错误“自签名应用以来,文档已被更改或损坏”。 下面是我在服务器上计算哈希的方法。 客户端对给定的哈希签名后,我将