【问题标题】:Itext sign pdf with external signature causes validation fail (“the document has been altered or corrupted..")带有外部签名的 Itext 签名 pdf 导致验证失败(“文档已被更改或损坏..”)
【发布时间】:2022-07-07 23:53:14
【问题描述】:

我尝试使用 itext7 签署 pdf 文档,从外部 Web 服务返回的证书和外部签名说签名服务:

我做了以下步骤:

  1. 得到原始 pdf,添加最后一页(签名页),上面有 2 个签名文件并创建临时 pdf

  2. 根据创建的临时 pdf 计算哈希

  3. 与签名服务交换我的 Base64 编码哈希和编码 Base64 签名哈希(我不确定这是原始签名还是 CMS 签名 - 我将其视为 CMS 容器)

  4. 解码并将获得的签名哈希与来自 Sign Company 的证书一起放入我在临时 pdf 文件上的 Sig 字段之一。我将需要在未来以这种方式签署后续字段。

不幸的是,我在 Adob​​e Reader 中遇到了验证错误:“自从应用签名后,文档已被更改或损坏”: link to Adobe validation result

在我创建标志页面的代码片段下方:

private void createPdfDocument(Document doc, int iteration) {
    //Add last sign page to doc
    doc.add(new AreaBreak(AreaBreakType.LAST_PAGE));
    doc.add(new AreaBreak(AreaBreakType.NEXT_PAGE));

    PdfPage lastPage = doc.getPdfDocument().getLastPage();
    float width = lastPage.getPageSize().getWidth();
    float height = lastPage.getPageSize().getHeight();

    createTitle(doc);
    PdfAcroForm form = PdfAcroForm.getAcroForm(doc.getPdfDocument(), true);

    for (int i = 1; i <= iteration; i++) {
        addSignArea(doc, form, VERTICAL_RECTANGLE_START - (i - 1) * VERTICAL_MARGIN,
                VERTICAL_TEXT_START - (i - 1) * VERTICAL_MARGIN, i);
    }
    System.out.println("Creating sign page finished");
}
private void addSignArea(Document doc, PdfAcroForm form, int verticalRectPosition, int verticalFieldPosition, int iteration) {
    Color color = new DeviceRgb(46, 66, 148);

    //Create sign area frame
    new PdfCanvas(doc.getPdfDocument().getLastPage())
            .roundRectangle(50, verticalRectPosition, 495, 50, 5)
            .setLineWidth(0.5f)
            .setStrokeColor(color)
            .stroke();

    //Create text fields inside frame
    PdfSignatureFormField signField = PdfSignatureFormField.createSignature(doc.getPdfDocument(),
            new Rectangle(50, verticalRectPosition, 495, 50));
    signField.setFieldName(getFieldCountedName("Signature", iteration));
    form.addField(signField);
}

我是这样计算文档哈希的:

public String getDocumentHash() {
    try (FileInputStream is = new FileInputStream(DOC)) {
        byte[] hash = DigestAlgorithms.digest(is, DigestAlgorithms.SHA256, null);
        String encodeToString = Base64.getEncoder().encodeToString(hash);
        System.out.println(encodeToString);
        return encodeToString;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

最后签署pdf文件:

public class DocumentSigner {

public static final String DEST = "";
private static final String SOURCE = "";
private static final String DOC_HASH = "6XsoKhEXVMu8e0R7BGtaKvghwL0GBrqTGAivFpct6J4=";

public static final String[] RESULT_FILES = new String[]{
        "sign_doc_result1.pdf"
};

public static void main(String[] args) throws GeneralSecurityException, IOException {
    File file = new File(DEST);
    file.mkdirs();

    Certificate[] chain = new Certificate[1];
    chain[0] = CertLoadTest.getPublicCert(); //load cert from path

    String encodedExternalHash = getExternalSignedHash(); //get the signded hash returned from the Sign Service

    new DocumentSigner().sign(SOURCE, DEST + RESULT_FILES[0], chain, PdfSigner.CryptoStandard.CMS,
            encodedExternalHash, DOC_HASH, "Signature1");
}

public void sign(String src, String dest, Certificate[] chain, PdfSigner.CryptoStandard subfilter,
                 String encodedExternalHash, String documentHash,  String fieldName) throws GeneralSecurityException, IOException {

    try (FileOutputStream os = new FileOutputStream(dest); InputStream is = new FileInputStream(src)) {
        PdfReader reader = new PdfReader(is);
        PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
        signer.setFieldName(fieldName);

        IExternalDigest digest = new BouncyCastleDigest();
        IExternalSignature signature = new CustomSignature(Base64.getDecoder().decode(encodedExternalHash),
                Base64.getDecoder().decode(documentHash), chain);

        signer.signDetached(digest, signature, chain, null, null, null,
                8096, subfilter);
    }
}

public class CustomSignature implements IExternalSignature {
    private byte[] signedHash;
    private byte[] documentHash;
    private Certificate[] chain;

    public CustomSignature(byte[] signedHash, byte[] documentHash, Certificate[] chain) {
        this.signedHash = signedHash;
        this.documentHash = documentHash;
        this.chain = chain;
    }

    public String getHashAlgorithm() {
        return DigestAlgorithms.SHA256;
    }

    public String getEncryptionAlgorithm() {
        return "RSA";
    }

    public byte[] sign(byte[] message) throws GeneralSecurityException {
        return signedHash;
    }
}

private static String getExternalSignedHash() {
    //mocked Sign Service result - documentHash is exchanged with signedHash
    return "3BLqVMOLSFXEfCy++n0DmRqcfCGCqSLy9Nzpn1IpAn6iTqr+h78+yOomGMAL0La77IB08Tou9gkxbwSXPHrdN5+EPm7HCXeI/z3fzj711H9OH6P9tWtVHgieKUFOVhrm/PTeypSC/vy7RJQLNmL5+/+Moby5Bdo/KaaN2h9Jj41w1i6CwL/7wzCZ0h+AU9sI+IC0i/UbWFFz7VMfN5barcF1vP+ECLiX3qtZrGbFZNZfrr+28ytNTdUR4iZJRLKL2nXeg0CqxsTjnAyUsFMTCro1qv0QkQO8Cv6AJFhWlUFGUkt+pIUKhIticlypB+WdzwmISOsRK0IUiKgrJI6E3g==";
}

A 还尝试将从 Sign Service 哈希返回的结果视为原始签名 - 这是 CustomSignature 类中的 sign 方法的样子:

        BouncyCastleDigest digest = new BouncyCastleDigest();
        PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, digest, false);
        byte[] sh = sgn.getAuthenticatedAttributeBytes(documentHash, PdfSigner.CryptoStandard.CMS, null, null);
        sgn.setExternalDigest(signedHash, null, "RSA");
        byte[] encodedSig = sgn.getEncodedPKCS7(documentHash, PdfSigner.CryptoStandard.CMS, null, null, null);
        return encodedSig;

但在这种情况下,我在 Adob​​e Reader 中遇到格式签名错误

我的流程是否正确,或者我可能需要另一种方法来正确签署文件。

【问题讨论】:

  • 您的流程不正确:在sign 中,您准备要签名的 PDF 并使用您作为参数提供的签名对其进行签名;因此,该签名是在已知要签名的数据之前创建的! (要签名的数据由签名准备完成。)
  • 请编辑问题以将其限制为具有足够详细信息的特定问题,以确定适当的答案。
  • @mkl 感谢您的评论,经过一番阅读,我认为我应该使用延迟签名过程和预签名,例如这里的示例git.itextsupport.com/projects/I7JS/repos/signatures/browse/src/…
  • 这是一种选择。另一种方法是从您的CustomSignature 中调用您的签名服务。哪个更好取决于您的签名服务和整个用例的详细信息。

标签: java pdf itext sign


【解决方案1】:

根据评论中发布的建议,我仍然使用自定义IExternalSignature 实现和外部调用签名方法:

    public void sign(Certificate[] chain, PdfSigner.CryptoStandard subfilter, String fieldName) throws GeneralSecurityException, IOException {
    try (InputStream is = new FileInputStream(src); FileOutputStream os = new FileOutputStream(dest)) {
        PdfReader reader = new PdfReader(is);
        PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
        signer.setFieldName(fieldName); //My signature fields
        IExternalDigest digest = new BouncyCastleDigest();
        IExternalSignature signature = new CustomSignature(chain);
        signer.signDetached(digest, signature, chain, null, null, null,
                8196, subfilter);
    }
}

public class CustomSignature implements IExternalSignature {
    private Certificate[] chain;
    public CustomSignature(Certificate[] chain) {
        this.chain = chain;
    }
    public String getHashAlgorithm() {
        return DigestAlgorithms.SHA256;
    }

    public String getEncryptionAlgorithm() {
        return "RSA";
    }

    public byte[] sign(byte[] message) throws GeneralSecurityException {
        BouncyCastleDigest digest = new BouncyCastleDigest();
        byte[] hash = digest.getMessageDigest("SHA256").digest(message);
        return Base64.getDecoder().decode(client.getSignedHash(Base64.getEncoder().encodeToString(hash))); // call externall service here
    }
}

对于第一次调用验证错误消失了,Signature1 似乎很好,但是当我尝试在第一次调用中使用 pdfgeneretaed 签署第二个 sig 字段并将另一个文件作为输出时出现问题。现在新创建的 Signature2 没问题,但第一个 bacam 失败,字节范围损坏:

new DocumentSigner(SOURCE, DEST1).sign(chain,PdfSigner.CryptoStandard.CMS, "Signature1");
new DocumentSigner(DEST1, DEST2).sign(chain, PdfSigner.CryptoStandard.CMS, "Signature2");

如果有任何想法,我将不胜感激如何签署多个字段而不破坏以前的字段

Here is Adobe validation output after the second call

【讨论】:

    猜你喜欢
    • 2022-01-01
    • 2021-11-06
    • 2021-06-20
    • 2016-09-16
    • 2021-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-03
    相关资源
    最近更新 更多