将SignedHash插入PDF以进行外部签名过程-WorkingSample

时间:2019-04-29 11:01:51

标签: java pdf itext

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

  • 客户只有PDF才能签名,只有公共证书
  • 外部硬件(带有私有证书)接受哈希并返回签名哈希

我试图这样做,但是PDF内的签名显示我在签名过程之后对该文件进行了修改。

以下代码获取原始PDF和公共证书,并创建带有空符号的临时pdf并返回哈希值

此哈希从外部发送到另一个远程应用程序(那里有对应的私有证书),并返回签名的哈希,我阅读了签名的哈希并将其添加到临时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中,作者说:

  

“经过大量调试,我们终于找到了问题。

     

出于某种神秘的原因,该生成哈希值的方法   文档,执行了两次,使第一个哈希无效(   用于发送给服务)。

     

在对代码进行重构之后,原始代码就可以了   正确。

     

非常感谢所有帮助我的人,尤其是mkl。”

,但未提供更多信息, 同样,刻印在压模上的时间和来自TSA的时间是故意不同的。我认为这不是问题。

一些提示?

谢谢

更新1

(先前的代码已更新)

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

screenshot2

现在从不使用sh var!

我将哈希字节[]发送给它,但是Adobe Reader再次说该文件已被修改。

也许我可以尝试使用“隐形签名”方法。还是“可见压模”在签名验证过程中没有区别?

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

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

Screenshot3

任何帮助将不胜感激。

谢谢

更新2-解决方案!!!

真的非常感谢mkl的回答!

有效的解决方法是我们需要在PKCS#7程序包内生成已签名/已认证属性的哈希!请参见signedAttributesHash变量中的原始代码 signed pdf image

1 个答案:

答案 0 :(得分:1)

您当前的代码

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

您签署hash,其计算方式为

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

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

您以前的代码

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

您曾经在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"));