带有iText的外部签名PDF

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带有iText的外部签名PDF相关的知识,希望对你有一定的参考价值。

首先,尽管我已经关注StackOverflow已有一段时间了,但这是我第一次发布内容,因此,如果我根据规则做错了什么,请随时向正确的方向指出。

我正在使用iText5开发PDF数字签名应用程序,在准备要签名的PDF之后,它依赖于外部服务来提供签名的哈希。

iText documentation中所述,在第一阶段中,我准备了PDF(在最终实现中,所有PDF都可能是多符号的,因此我使用附加模式),如下所示:

public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, List<Org.BouncyCastle.X509.X509Certificate> certificateChain) 
        // we create a reader and a stamper
        using (PdfReader reader = new PdfReader(unsignedPdf)) 
            using (FileStream baos = File.OpenWrite(tempPdf)) 

                List<Org.BouncyCastle.X509.X509Certificate> chain = certificateChain;
                PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
                sap                   = pdfStamper.SignatureAppearance;
                sap.Certificate       = certificateChain[0];
                sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, signatureFieldName);
                //sap.SetVisibleSignature(signatureFieldName);
                sap.SignDate          = DateTime.Now;
                PdfSignature dic      = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);  
                dic.Date              = new PdfDate(sap.SignDate);
                dic.Name              = CertificateInfo.GetSubjectFields(chain[0]).GetField("CN");
                sap.CryptoDictionary  = dic;
                sap.Certificate       = certificateChain[0];
                sap.Acro6Layers       = true;
                sap.Reason            = "test";
                sap.Location          = "test";

                IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
                MakeSignature.SignExternalContainer(sap, external, 8192);
                signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
                byte[] hash = DigestAlgorithms.Digest(sap.GetRangeStream(), "SHA256");
                //byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);

                return hash;
            
        
    

在此步骤之后,我将哈希发送到外部服务,该服务返回签名的哈希。

检查我发送给服务的哈希,这似乎是正确的,因为它涵盖了除新签名内容之外的所有PDF。

然后我使用以下方法结束签名过程:

private byte[] Sign(PdfPKCS7 signatureContainer, List<X509Certificate2> chain2, List<Org.BouncyCastle.X509.X509Certificate> chain, byte[] hash, byte[] signedBytes, string tmpPdf, string signedPdf, string signatureFieldName) 
        System.Security.Cryptography.RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = chain2[0].PublicKey.Key as System.Security.Cryptography.RSACryptoServiceProvider;
        bool verify = publicCertifiedRSACryptoServiceProvider.VerifyHash(hash, "SHA256", signedBytes); //verify if the computed hash is same as signed hash using the cert public key
        Console.WriteLine("PKey signed computed hash is equal to signed hash: " + verify);

        AsnEncodedData asnEncodedData = new AsnEncodedData(signedBytes);
        Console.WriteLine(asnEncodedData.Format(true));

        //ITEXT5
        try 
            //Console.WriteLine("Signed bytes: " + Encoding.UTF8.GetString(signedBytes));

            using (PdfReader reader = new PdfReader(tmpPdf)) 
                using (FileStream outputStream = File.OpenWrite(signedPdf)) 
                IExternalSignatureContainer external = new Objects.MyExternalSignatureContainer(signedBytes, chain, signatureContainer);
                MakeSignature.SignDeferred(reader, signatureFieldName, outputStream, external);
                
            
            return new byte[]  ;
        
        catch(Exception ex) 
            File.Delete(tmpPdf);
            Console.WriteLine("Error signing file: " + ex.Message);
            return new byte[]  ;
        
    

在Sign方法的开头,我验证发送给使用相同证书签名的外部服务的哈希是否等于外部服务响应,这是正确的。

MyExternalSignatureContainer代码:

public class MyExternalSignatureContainer : IExternalSignatureContainer 
        private readonly byte[] signedBytes;
        public List<Org.BouncyCastle.X509.X509Certificate> Chain;
        private PdfPKCS7 sigField;

        public MyExternalSignatureContainer(byte[] signedBytes) 
            this.signedBytes = signedBytes;
        

        public MyExternalSignatureContainer(byte[] signedBytes, List<Org.BouncyCastle.X509.X509Certificate> chain, PdfPKCS7 pdfPKCS7) 
            this.signedBytes = signedBytes;
            this.Chain = chain;
            this.sigField = pdfPKCS7;
        

        public byte[] Sign(Stream data) 
            try 
                sigField.SetExternalDigest(signedBytes, null, "RSA");
                return sigField.GetEncodedPKCS7(signedBytes, null, null, null, CryptoStandard.CMS);
            
            catch (IOException ioe) 
                throw ioe;
            
        

        public void ModifySigningDictionary(PdfDictionary signDic) 
        
    

问题是当我在Acrobat中打开PDF时,它指出自应用签名以来,该文档已被修改或损坏。

((如果我在PDF-XChange中打开相同的PDF,则表明该PDF未被修改)。

到目前为止我没有碰到的尝试:

无法完全确定外部服务是否使用SHA256,我已经尝试将摘要的摘要更改为预签名的SHA1,从而在Acrobat Reader中导致“格式错误”。

[就像在StackOverlow的另一篇文章中提到的一样(我找不到链接的文章),潜在的问题是临时文件使用不同的流。我已经尝试过使用相同的流,但是也没有运气。

PDF的样本:

Original file

Temp File

Signed File

Base64哈希发送到服务:

XYfaS/SisA/tk5hcl035RpBjOczrH9E5rgiAMpqgkjI=

Base64签名的哈希作为响应发送:

CnV3WL7skhMCtZG1r1Qi2oyE9WPO3KP4Ieu/Xm4lec+DAbYbhQxCvjMISsG3sTwYY7Lqi4luD60uceViDH848rS9OkTn8szzAnnX2fSYIwqDpG3qjJAb6NOXEv41hy+XYhSBJWS4ji2mM2ReruwPafxB1aM25L5Jyd0V7WecuNFUevUrvd85Y2KBkyBw9zCA8NDAQPPY0UT4GkXZi3Z35+Sf/s2o8zxCOlBDaIJyMvJ9De79nw4jC5L9NesHpFxx3mX1g1N33GHjUNdETgFMhnd8RDUlGLW6bsAyv78gvwE6aXF6COObap/VtlLvMOME68MzLr6izKte6uA35Zwj9Q==


mkl's answer之后更新:

根据答案,我仅在一个阶段中更改了代码签名文档的代码,并最终使用以下方法:

using (PdfReader reader = new PdfReader(fileLocation)) 
    using (FileStream baos = File.OpenWrite(tmpFile)) 

        List<Org.BouncyCastle.X509.X509Certificate> chain = Chain;
        PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
        PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
        sap.Certificate = Chain[0];
        sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, signatureFieldName);
        //sap.SetVisibleSignature(signatureFieldName);
        sap.SignDate = DateTime.Now;
        PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
        dic.Date = new PdfDate(sap.SignDate);
        dic.Name = CertificateInfo.GetSubjectFields(chain[0]).GetField("CN");
        sap.CryptoDictionary = dic;
        sap.Certificate = Chain[0];
        sap.Acro6Layers = true;
        //sap.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS;
        sap.Reason = "test";
        sap.Location = "test";

        IExternalSignature signature = new Objects.RemoteSignature(client, signatureRequest);
        MakeSignature.SignDetached(sap, signature, Chain, null, null, null, 8192, CryptoStandard.CMS);

    

和IExternalSignature实现:

public virtual byte[] Sign(byte[] message) 
    IDigest messageDigest = DigestUtilities.GetDigest(GetHashAlgorithm());
    byte[] messageHash = DigestAlgorithms.Digest(messageDigest, message);
    //
    // Request signature for hash value messageHash
    // and return signature bytes
    //
    signatureRequest.Hash = messageHash;
    SignatureService.SignatureResponse signatureResponse = client.Signature(signatureRequest);

    if (signatureResponse.Status.Code == "00") 
         return signatureResponse.DocumentSignature;
    
    else 
        throw new Exception("Error signing file: " + signatureResponse.Status.Message);
    

signatureResponse.DocumentSignature表示服务返回的已签名字节。

现在在结果PDF中,我收到BER解码错误。

分析您的示例PDF,您似乎宣告了错误的证书为签署者证书

尽管我知道当前证书无效,但是它是由服务提供的,并且在该服务的先前实现中,我将发送整个PDF进行签名,已签名的PDF也使用此证书进行了签名。

一个问题:知道在两阶段签名中,我能够使用此证书对PDF进行签名(除了签名错误后更改或损坏的文档),这种方法是否也应适用于相同的方法证书?

当前,正在发生的事情是:

Signature in Acrobat Reader

检查签名:

Signature properties

同样,如果我在PDF-XChange中打开相同的PDF,则签名有效且未修改文档。要求PDF在Acrobat中有效,但读者之间的这种差异使我感到困惑。

Result PDF


更新2

即您只需要在字节序列前为哈希添加前缀30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20。

在将SHA256前缀添加到消息摘要之后,现在已经正确签名了生成的PDF。

Adobe Reader会接受固定签名吗?

我对此表示怀疑。签署者证书的密钥用法仅包含用于签署其他证书的值。

当前证书仅用于测试。在生产环境中,我相信外部服务提供的证书将是有效的。

关于这个问题,我还有两个问题:

对于您的代码,这意味着您必须先将哈希打包到DigestInfo结构中,然后再将其发送到服务。

问:您如何检查签名容器以得出不正确的结论?

Q:在我的初始代码中,我有两个阶段的签名。在单符号方法中应用的相同主体仍然有效,即,使用SHA256前缀执行预签名字节并在将摘要设置为结果带符号字节之后吗?

答案

您的代码中存在许多问题。

另一答案

[请您帮助我了解c#的最终代码版本。我也有完全相同的要求,但有两个阶段签名。

以上是关于带有iText的外部签名PDF的主要内容,如果未能解决你的问题,请参考以下文章

使用iTExt 5.3获取pdf签名

Java IText7 PDF 签名问题 - 文档自签名后已被更改或损坏

当 pdf 由假名证书进行数字签名时,签名信息未正确显示

关于可见的数字签名

iText签名OcspClient无法获得OCSP

iText:在可见签名上显示签名细节