java - 将 SignedHash 插入 PDF 中以进行外部签名过程 -workingSample

标签 java pdf itext

遵循电子书第 4.3.3 节“Digital Signature for PDF document” 我正在尝试创建一个工作示例,其中:

  • 客户有一个 PDF 需要签名,并且只有一个公共(public)证书
  • 外部硬件(带有私有(private)证书)采用哈希值并返回签名哈希值

我尝试这样做,但 PDF 内的签名显示该文件在签名过程后已被修改。

以下代码获取原始 PDF 和公共(public)证书,并创建一个带有空符号的临时 pdf 并返回 HASH

此哈希从外部发送到另一个远程应用程序(其中有对应的私有(private)证书)并返回签名的哈希,我读取签名的哈希并将其添加到临时 pdf 中。

更新了完整的工作代码:

package com.Marloo;


import org.apache.commons.codec.Charsets;
import org.bouncycastle.util.encoders.Base64;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;

import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;

public class Test {

    public static final String CERT = "src/main/resources/certificate.pem";
    public static final String SRC = "src/main/resources/tmp.pdf";
    public static final String DEST = "src/main/resources/signed.pdf";

    public static void main(String args[]) throws IOException {
        getHash(SRC, CERT);
    }



    public static void getHash(String doc, String cert) throws IOException {

        try {

            File initialFile = new File(cert);
            InputStream is = new FileInputStream(initialFile);

            // We get the self-signed certificate from the client
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            Certificate[] chain = new Certificate[1];
            chain[0] = factory.generateCertificate(is);

            // we create a reader and a stamper
            PdfReader reader = new PdfReader(doc);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0');

            // we create the signature appearance
            PdfSignatureAppearance sap = stamper.getSignatureAppearance();
            sap.setReason("TEST REASON");
            sap.setLocation("TEST LOCATION");
            //sap.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig"); //visible
            sap.setVisibleSignature(new Rectangle(36, 748, 36, 748), 1, "sig"); //invisible
            sap.setCertificate(chain[0]);

            // we create the signature infrastructure
            PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
            dic.setReason(sap.getReason());
            dic.setLocation(sap.getLocation());
            dic.setContact(sap.getContact());
            dic.setDate(new PdfDate(sap.getSignDate()));
            sap.setCryptoDictionary(dic);
            HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
            exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
            sap.preClose(exc);
            ExternalDigest externalDigest = new ExternalDigest() {
                public MessageDigest getMessageDigest(String hashAlgorithm)
                        throws GeneralSecurityException {
                    return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
                }
            };
            PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
            InputStream data = sap.getRangeStream();
            byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));


            // we get OCSP and CRL for the cert
            OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
            OcspClient ocspClient = new OcspClientBouncyCastle(ocspVerifier);
            byte[] ocsp = null;
            if (chain.length >= 2 && ocspClient != null) {
                ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
            }

        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);
        InputStream sh_is = new ByteArrayInputStream(sh);
        byte[] signedAttributesHash = DigestAlgorithms.digest(sh_is, externalDigest.getMessageDigest("SHA256"));


        System.out.println("----------------------------------------------");
        System.out.println("Hash to be sign:");
        System.out.println( new String(Base64.encode(signedAttributesHash), Charsets.UTF_8));
            System.out.println("----------------------------------------------");
            System.out.println("Insert b64 signed hash [ENTER]");
            System.out.println("----------------------------------------------");

            Scanner in = new Scanner(System.in);
            String signedHashB64 = in.nextLine();
            System.out.println( signedHashB64);

            ByteArrayOutputStream os = baos;

            byte[] signedHash = org.apache.commons.codec.binary.Base64.decodeBase64(signedHashB64.getBytes());

            // we complete the PDF signing process
            sgn.setExternalDigest(signedHash, null, "RSA");
            Collection<byte[]> crlBytes = null;
            TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle("http://timestamp.gdca.com.cn/tsa", null, null);

            byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
            byte[] paddedSig = new byte[8192];
            System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
            PdfDictionary dic2 = new PdfDictionary();
            dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

            try {
                sap.close(dic2);
            } catch (DocumentException e) {
                throw new IOException(e);
            }

            FileOutputStream fos = new FileOutputStream(new File(DEST));
            os.writeTo(fos);

            System.out.println("pdfsig " + System.getProperty("user.dir") + "/" + DEST);
            System.out.println("------------------End Of Life --------------------------");

            System.exit(0);


        } catch (GeneralSecurityException e) {
            throw new IOException(e);
        } catch (DocumentException e) {
            throw new IOException(e);
        }

    }


}

这里有一些screenshot

一些提示: 在这个不完整的post作者说:

"After much debugging, we finally found the problem.

For some mysterious reason, the method that generates the hash of the document, was executed twice, invalidating the first hash (which we use to send to the service).

After a refactoring work of the code, the original code worked correctly.

Very thanks to all people that help me, especially mkl."

但没有提供更多信息, 印章上刻的时间和 TSA 的时间也故意不同。我想这不会是问题。

一些提示?

谢谢

更新 1

(之前的代码已更新)

外部服务在输入中不接受整个 Sign 结构,而只接受 32 字节哈希

screenshot2

现在 sh var 从未被使用过!

我获取哈希字节[],发送给它,但 Adob​​e Reader 再次提示该文件已被修改。

也许我可以尝试使用“隐形签名”方法。或者“可见印章”在签名验证过程中没有产生影响?

或者也许我需要使用签名字节以某种方式重新创建 ANS.1 结构,然后对文档进行签名?

也许 tsa 和标志之间的时间必须相同?

Screenshot3

任何形式的帮助将不胜感激。

谢谢

更新 2 - 有效解决方案!!!

真的真的真的非常感谢mkl的回答!

有效的修复是我们需要在 PKCS#7 包内生成签名/验证属性的哈希值!!!请参阅 signedAttributesHash 变量中的原始代码 signed pdf image

最佳答案

您当前的代码

您当前的代码签署了完全错误的哈希值。

您签署哈希,计算公式为

InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));

即您直接对文档的签名范围的哈希进行签名。这是错误的,因为您正在构建具有签名属性的 PKCS#7 签名容器,即文档签名范围的哈希必须是这些签名属性之一的值,并且您必须对签名属性的哈希进行签名!

您以前的代码

您以前的代码也签署了错误的字节,但更接近正确的字节。

您曾经签署last32,计算结果为

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, null, MakeSignature.CryptoStandard.CMS);
byte[] last32 = Arrays.copyOfRange(sh, sh.length - 32, sh.length);

即您正确生成了签名属性字节(也称为经过身份验证的属性字节),其中包含作为属性值的 hash,但随后您只需获取其中的最后 32 个字节。这是错误的,您必须对已签名属性字节的哈希值进行签名,而不是它们的最后 32 个字节。

什么应该有效

您应该签署signedAttributesHash,即签名属性字节的哈希值,即

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, null, MakeSignature.CryptoStandard.CMS);
byte[] signedAttributesHash = DigestAlgorithms.digest(new ByteArrayInputStream(sh), externalDigest.getMessageDigest("SHA256"));

关于java - 将 SignedHash 插入 PDF 中以进行外部签名过程 -workingSample,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55901977/

相关文章:

iText PDFWriter - 如果少数表格行转到新页面,则写入表格标题

java - 通过 Java 批处理 : iText or Apache FOP? 简单生成 PDF

java - 尝试查看任何资源的文档时,eclipse 中没有 javadoc

java - 什么是 "th"、 "python"、 "python3"、 "java"等...用于运行脚本的命令前面,称为?

java - 内部使用可以调用Facade吗?

java - 将多行电子表格转换为 pdf 的简洁方法

java - 我需要知道如何让我的程序输出我输入的单词以及使用二维数组重新排列的新单词

php - dompdf: "Maximum execution time of 30 seconds exceeded"

c# - 使用 Xamarin Android 创建 PDF

java - 编辑浏览器嵌入的pdf并将pdf直接保存到服务器