将SignedHash插入PDF以进行外部签名过程-WorkingSample [英] Insert a SignedHash into PDF for external signing process - WorkingSample

查看:158
本文介绍了将SignedHash插入PDF以进行外部签名过程-WorkingSample的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

遵循电子书第4.3.3节" PDF文档的数字签名" 我正在尝试创建一个有效的示例,其中:

  • 客户拥有PDF签名,只有公共证书
  • 外部HW(带有私有证书)接受HASH并返回SIGNED HASH

我尝试这样做,但是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);
        }

    }


}

这里有一些

一些提示: 在这篇不完整的帖子中,作者说:

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

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

在对代码进行重构之后,原始代码就可以正常工作了 正确.

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

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

一些提示?

谢谢

更新1

(先前的代码已更新)

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

现在从未使用过sh var!

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

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

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

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

任何帮助将不胜感激.

谢谢

更新2-解决方案!!!

真的非常感谢mkl的回答!

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

解决方案

您当前的代码

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

您签署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"));

Following the eBook section 4.3.3 "Digital Signature for PDF document" I'm trying to create a working example where :

  • the client has a PDF to sign and only a public Cert
  • the External HW (with private cert) takes an HASH and returns a SIGNED HASH

I tried to do it, but the signature inside the PDF shows me that the file was modified after the signing process.

The following code takes the original PDF and a public certificate and create a temporary pdf with empty sign and return an HASH

This hash is sent externally to another remote application (where there is a corrispondent Private cert) and returns the signed hash, I read the signed hash and added it into the temporary pdf.

Full Working Code updated:

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);
        }

    }


}

Here some

Some hint: in this incomplete post the author says:

"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."

but no futher information was provided, also the Time write on the stamper and the time from TSA is different on purpose. I think this wouldn't be the problem.

Some hints ?

Thanks

Update 1

(the previus code was updated)

the external services doesn't accept in input the whole Sign structure but only the 32byte hash

now the sh var is never used !

i take the hash byte[], send to it but again Adobe Reader say that the file was modified.

Maybe can i try with the "invisible signature" method. Or the "visible stamper" didn't make difference in the signature validation process ?

Or Maybe i need to recreate a ANS.1 structure somehow with the signed byte and then sign the doc ?

Maybe the time must be the same between the tsa and the sign ?

Any kind of help would be appreciates.

Thanks

Update 2 - WORKING SOLUTION !!!

Really really really thanks to mkl for the answer !

the working fix was that we need to generate the hash of the signed/authenticated attributes inside the PKCS#7 package !!! See the original code at the signedAttributesHash variable

解决方案

Your current code

Your current code signs the entirely wrong hash.

You sign hash which is calculated as

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

I.e. you directly sign the hash of the signed ranges of the document. This is wrong because you are building a PKCS#7 signature container with signed attributes, i.e. the hash of the signed ranges of the document must be the value of one of those signed attributes and you have to sign the hash of the signed attributes!

Your former code

Your former code signed the wrong bytes, too, but was nearer to the correct bytes.

You used to sign last32 which was calculated as

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

I.e. you correctly generated the signed attribute bytes (aka authenticated attribute bytes) with hash included as attribute value, but then you simply took the last 32 bytes thereof. This is wrong, you must sign the hash of the signed attribute bytes, not their final 32 bytes.

What should work

You should sign signedAttributesHash, the hash of the signed attribute bytes, i.e.

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

这篇关于将SignedHash插入PDF以进行外部签名过程-WorkingSample的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆