当前位置: 首页 > 知识库问答 >
问题:

使用iTextsharp外部签名PDF(3)

白淇
2023-03-14

需要通过使用外部webservice对文档哈希进行签名来签署PDF,该过程必须在2个步骤中完成,并使用临时空签名。

在Priyanka问题和Grazina问题之后,阅读了那些帖子上的mkl答案,我现在有一个无效的签名,即使像Grazina那样添加了哈希前缀。

iTextSharp版本:5.5.13.1

这个节目是我上一个问题的另一个问题。当前代码(编译并开始调用SignPDF方法):

public class PDFSigner
{
    private const string SIG_FIELD_NAME = "sigField1";

    private void SignPDF(string pdfFilePath, string userId)
    {
        var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";
        var signedPdfFilePath = $"{pdfFilePath}.signed.pdf";

        //Get certificates chain from webservice
        var certificatesChain = this.GetUserCertificates(userId);

        byte[] hash = this.CreatePDFEmtySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);

        //Get signature from webservice
        byte[] signedHash = this.GetSignature(hash, userId);

        CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);
    }

    private byte[] CreatePDFEmtySignature(string pdfFilePath, string preparedSigPdfFilePath, List<Org.BouncyCastle.X509.X509Certificate> certificatesChain)
    {
        byte[] hash = null;

        using (PdfReader reader = new PdfReader(pdfFilePath))
        {
            using (FileStream baos = File.OpenWrite(preparedSigPdfFilePath))
            {
                PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
                PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
                sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, SIG_FIELD_NAME);

                //TODO: check how to select the correct certificate, have 3 items on list, selected leaf after debug (first one)
                sap.Certificate = certificatesChain.First();

                var externalEmptySigContainer = new MyExternalEmptySignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED, preparedSigPdfFilePath);

                MakeSignature.SignExternalContainer(sap, (IExternalSignatureContainer)externalEmptySigContainer, 8192);

                hash = externalEmptySigContainer.PdfHash;
            }
        }
        return hash;
    }

    private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath, 
        byte[] hash, byte[] signedHash, List<Org.BouncyCastle.X509.X509Certificate> certificatesChain)
    {
        using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
        {
            using (FileStream baos = File.OpenWrite(signedPdfFilePath))
            {
                IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(signedPdfFilePath, hash, signedHash, certificatesChain);
                MakeSignature.SignDeferred(reader, SIG_FIELD_NAME, baos, externalSigContainer);
            }
        }
    }

    public class MyExternalEmptySignatureContainer : ExternalBlankSignatureContainer
    {
        public string PdfTempFilePath { get; set; }
        public byte[] PdfHash { get; private set; }

        public MyExternalEmptySignatureContainer(PdfName filter, PdfName subFilter, string pdfTempFilePath) : base(filter, subFilter)
        {
            this.PdfTempFilePath = pdfTempFilePath;
        }

        override public byte[] Sign(Stream data)
        {
            byte[] sigContainer = base.Sign(data);

            //Get the hash
            IDigest messageDigest = DigestUtilities.GetDigest("SHA-256");
            byte[] messageHash = DigestAlgorithms.Digest(data, messageDigest);

            #region Log
            var messageHashFilePath = $"{this.PdfTempFilePath}.messageHash-b64.txt";
            File.WriteAllText(messageHashFilePath, Convert.ToBase64String(messageHash));
            #endregion Log

            //Add hash prefix
            byte[] sha256Prefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
            byte[] digestInfo = new byte[sha256Prefix.Length + messageHash.Length];
            sha256Prefix.CopyTo(digestInfo, 0);
            messageHash.CopyTo(digestInfo, sha256Prefix.Length);

            #region Log
            var messageHashWithPrefixFilePath = $"{this.PdfTempFilePath}.messageHash-with-prefix-b64.txt";
            File.WriteAllText(messageHashWithPrefixFilePath, Convert.ToBase64String(digestInfo));
            #endregion Log

            this.PdfHash = digestInfo;

            return sigContainer;
        }
    }

    public class MyExternalSignatureContainer : IExternalSignatureContainer
    {
        public byte[] Hash { get; set; }
        public byte[] SignedHash { get; set; }
        public List<Org.BouncyCastle.X509.X509Certificate> CertificatesList { get; set; }

        public MyExternalSignatureContainer(string signedPdfFilePath, byte[] hash, byte[] signedHash, List<Org.BouncyCastle.X509.X509Certificate> certificatesList)
        {
            this.Hash = hash;
            this.SignedHash = signedHash;
            this.CertificatesList = certificatesList;
        }

        public byte[] Sign(Stream data)
        {
            PdfPKCS7 sgn = new PdfPKCS7(null, this.CertificatesList, "SHA256", false);
            sgn.SetExternalDigest(this.SignedHash, null, "RSA");
            return sgn.GetEncodedPKCS7(this.Hash, null, null, null, CryptoStandard.CMS);         
        }

        public void ModifySigningDictionary(PdfDictionary signDic)  {   }
    }

    public byte[] GetSignature(byte[] hash, string userId)
    {
        // Request signature for hash value messageHash and return signature bytes               
        byte[] signature = null;

        //CALL WEBSERVICE:
        //signature = WEBSERVICE_CALL_TO_GET_SIGNED_HASH(hash, userId);

        return signature;
    }

    private List<Org.BouncyCastle.X509.X509Certificate> GetUserCertificates(string userId)
    {
        List<Org.BouncyCastle.X509.X509Certificate> certChain = null;

        //CALL WEBSERVICE:
        //certChain = WEBSERVICE_CALL_TO_GET_SIGNED_CERTIFICATES(userId);

        return certChain;
    }
}

获得的结果

消息哈希(基64):

lA2cMByHLkuNdd+aHJRDy3GD2VIeIpVtzlgQGsq3cJw=
MDEwDQYJYIZIAWUDBAIBBQAEIJQNnDAchy5LjXXfmhyUQ8txg9lSHiKVbc5YEBrKt3Cc
LURoF4w3H7uwR3xltjZTBbxBlTCCyD5AqVfseg9F1jn9lfnJ4KAqDL85s2ABSN7iieqjhUd0/U7fReT8gmRV5ZVyjGZcA4BaXr9Lx5E8vLerrHfbE3lsqb4Qm4/3oWX7BjNjfK4ptrBLIaYiDW28sxRKev5mdoo9W2ecIPWAaD8wyrKG/sXj62FQsmetdB0Rzd5rPNbsjVhOeei2V1g1PgF7evJZAz6+1smIWHXPgpxQJ8gZG6KcnHy8N43TGxQ0yV6DKqpl5DGEgqDwiXUY2kGglYNkdaS/5bQy941j7AyEDulni8YXtQ+XH2opuq1OkqVPipLqQnk3DYMPQUzjWqatI1Awfhv4fnceZ2djxgpgtv03tM5PzpHmelXr1gGfcChNDA603SJr+9XVok35mslx13kv+03M4aa2Myp4JKPSNQBuqdeiXKMsXilgv1M13xdbaFL35Omq9ciQbts4kRPpeLj+9PC+kHsyrerRO8pSxHcEjojPqTdYT+pWAmlU
Certificates:
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 7590871326079402939
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 1738456118788016053
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 8957244856106358046

Attribute Certificates: none

CRLs: none

SignerInfo: [REMOVED] 8957244856106358046
Certificate: [REMOVED]
Signed attribute 1.2.840.113549.1.9.4 (PKCS 9 - Message Digest)

Digest: 
3031300D060960864801650304020105000420940D9C301C872E4B8D75DF9A1
C9443CB7183D9521E22956DCE58101ACAB7709C

Signed attribute 1.2.840.113549.1.9.3 (PKCS 9 - Content Type)

Signed Attributes Hash: 
08767823328F202C1C3E5DB543785ED591C6D84D23DAF3DCBB83684B987008CB
Signed Attributes Hash Hash: 
1E2D10B23CD772D16987126182E51BD4D827DB58C497BA4129BB533A576E3548
!!! Decrypted RSA signature is not PKCS1 padded: Decryption error
Decrypted signature bytes: [REMOVED]
!!! Decrypted RSA signature does not end with the PSS 0xbc byte either

!!! Signature does not validate with certificate

将尝试添加填充到签名,感谢帮助。

共有1个答案

宫坚
2023-03-14

首先,根据结果PDF判断,GetUserCertification调用似乎不会返回GetSignature稍后返回的签名的签名者证书,尽管使用了相同的userid值。

这由粘贴到问题中的Analyzesignatures输出表示:

SignerInfo: [REMOVED] 8957244856106358046
Certificate: [REMOVED]

签名容器中单个signerinfo中的SID与包含的证书集中的单个证书匹配。

!!! Decrypted RSA signature is not PKCS1 padded: Decryption error
Decrypted signature bytes: [REMOVED]
!!! Decrypted RSA signature does not end with the PSS 0xbc byte either

该证书的公钥是RSA密钥,signerinfo签名值的长度与密钥长度匹配,但使用该密钥解密该值既不会返回PKCS#1 v1.5填充的内容,也不会返回PSS结构。因此,“签名值”要么根本不是RSA签名值,要么就是使用与所称签名人证书中的公钥不匹配的私钥生成的签名。

因此,首先要做的是分析

    //CALL WEBSERVICE:
    //signature = WEBSERVICE_CALL_TO_GET_SIGNED_HASH(hash, userId);

而且

    //CALL WEBSERVICE:
    //certChain = WEBSERVICE_CALL_TO_GET_SIGNED_CERTIFICATES(userId);

这里的PDF签名帧包含一些错误。上一个问题“外部签名PDF with iText(2)[closed]”中的代码看起来更正确,因此我建议在找到此处签名web服务的正确用法后,将下一个方法建立在此处的代码基础上。

然而,这里有两个错误的解释很快引起了我的注意:

c# prettyprint-override">byte[] hash = this.CreatePDFEmtySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);

//Get signature from webservice
byte[] signedHash = this.GetSignature(hash, userId);

CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);

CreatePdFemtySignature返回的哈希是类MyExternalEmptySignatureContainer尝试确定带符号字节范围的哈希的结果。

但通常在CMS签名容器的上下文中(除了最原始的类型),实际的签名字节不是直接为文档字节创建的,而是为属性结构(所谓的“签名属性”或“认证属性”)创建的;文档字节的哈希值只是其中一个属性的值。

因此,由CreateFinalSignature中的GetSignature通过MyExternalSignatureContainer.sign返回的Hash的签名值SignedHash被注入到PKCS#7/CMS签名容器SignerInfo中,该容器的身份验证属性具有完全不同的哈希。

使用iTextpdfpkcs7类生成签名容器时,必须为pdfpkcs7.getauthenticatedattributeBytes结果的哈希值计算签名值字节,其参数与后面的pdfpkcs7.getencodedpkcs7调用的参数相对应。

因此,这样签名的PDF的实际签名字节的验证必须失败。

CreatePdFemtySignature返回的哈希包装在DigestInfo结构中(通过在MyExternalEmptySignatureContainer.Sign中相应地预置一些字节)。

通过MyExternalSignatureContainer.Sign中的CreateFinalSignature稍后将其作为签名字节的哈希值提供给PDFPKCS7。但在这里,它应该是裸露的,而不是包装在digestinfo结构中。

因此,对像这样签名的PDF的签名文档字节的哈希值的验证必须失败。

 类似资料:
  • 目标是实现一个PDF签名过程,在该过程中,服务器(.NET核心服务)根据请求(Electronic)向客户端提供要签名的散列。然后,客户端使用通过PKCS#11接口从智能卡获得的私钥对给定散列进行签名。然后将签名发送回服务器,以便使用iTextSharp将其附加到PDF文件中。 使用node-webcrypto-p11使用智能卡令牌签名哈希的过程目前非常简单(需要进行大量的尝试和错误)。采用的算法

  • 我正在尝试使用远程web服务来演唱pdf,该服务返回一个XML签名,该签名由签名和最终用户证书组成。 我需要使用此签名通过IText签名对pdf进行签名,因为web服务。 所有IText示例都使用消息格式,但我不确定应该如何处理XML签名。 打开临时Pdf并嵌入接收到的签名的代码 从web服务返回的XML签名: 当我将返回的签名与上面的代码一起使用时,签名验证失败,出现“错误遇到时BER解码”。

  • 我正在编写一个服务,其中我用一个空容器预签名pdf文件,从pdf文件中提取一个字节范围的散列,并将其发送到另一个服务,这将允许用户使用移动电话对散列进行签名。我拿回一个证书,我将注入到预签名pdf文件中的签名容器中。 签名本身起作用,数字签名是有效的,但我只需要更改可见签名本身的文本。我认为这是可能的,因为可见签名实际上与证书本身没有任何关系,所以显示来自证书的名称只是一种方便,特别是在多个签名的

  • Client=My application,Server=MSSP(移动签名服务提供商) 服务器仅对哈希值进行签名。 要签名数据: *Base64编码的SHA-384待签名数据摘要。(64个字符) *Base64编码的SHA-512待签名数据摘要。(88个字符) *要签名的Base64编码的DER编码的PKCS#1摘要信息。 注意:我使用MSSP(移动签名服务提供商)架构。对于SHA256算法,D

  • 首先,虽然我关注StackOverflow已经有相当一段时间了,但这是我第一次发布一些东西,所以如果我做错了或者不按规则做的话,请随时为我指出正确的方向。 我正在开发一个PDF数字签名应用程序,使用iText5,它依赖于一个外部服务,在我准备好PDF签名后提供一个签名哈希。 如iText文档中所述,在第一阶段,我准备了PDF(在最终实现中,所有PDF都可能是多签名的,因此我使用追加模式),如下所示

  • 目标是实现一个PDF签名过程,其中服务器根据请求向客户端提供要签名的哈希。然后,客户端使用通过PKCS#11接口从智能卡获得的私钥对给定哈希进行签名。然后,签名被发送回服务器,以便使用iTextSharp 5.5.4附加到PDF文件中。 在Acrobat Reader中查看签名时,我发现错误“自签名应用以来,文档已被更改或损坏”。 下面是我在服务器上计算哈希的方法。 客户端对给定的哈希签名后,我将