代码之家  ›  专栏  ›  技术社区  ›  yakya

C pKCS7智能卡数字签名-文件已被更改或损坏,因为它是签署的

  •  2
  • yakya  · 技术社区  · 8 年前

    我尝试用我的智能卡(USB令牌)签署PDF文件,但遇到 "Document has been altered or corrupted since it was signed" 在Adobe中打开签名的PDF文件时出错。这个错误并不是那么具有描述性,我不知道该看哪里,因为代码对我来说似乎很好,但显然不是。

    我使用的代码是:

    var signer = smartCardManager.getSigner("myTokenPassword");
    var toBeSignedHash = GetHashOfPdf(File.ReadAllBytes(@"xxx\pdf.pdf"), cert.asX509Certificate2().RawData, "dsa", null, false);
    var signature = signer.sign(toBeSignedHash);
    var signedPdf = EmbedSignature(cert.getBytes(), signature);
    File.WriteAllBytes(@"xxx\signedpdf.pdf", signedPdf);
    
    public byte[] GetHashOfPdf(byte[] unsignedFile, byte[] userCertificate, string signatureFieldName, List<float> location, bool append)
    {
        byte[] result = null;
    
        var chain = new List<Org.BouncyCastle.X509.X509Certificate>
        {
            Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(userCertificate))
        };
        Org.BouncyCastle.X509.X509Certificate certificate = chain.ElementAt(0);
        using (PdfReader reader = new PdfReader(unsignedFile))
        {
            using (var os = new MemoryStream())
            {
                PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0', null, append);
                PdfSignatureAppearance appearance = stamper.SignatureAppearance;
                appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(0,0,0,0), 1, signatureFieldName);
                appearance.Certificate = certificate;
                IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
                MakeSignature.SignExternalContainer(appearance, external, 8192);
                Stream data = appearance.GetRangeStream();
                byte[] hash = DigestAlgorithms.Digest(data, "SHA256");
                var signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
                byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
                result = DigestAlgorithms.Digest(new MemoryStream(signatureHash), "SHA256");
                this.hash = hash;
                this.os = os.ToArray();
                File.WriteAllBytes(@"xxx\temp.pdf", this.os);
            }
        }
    
        return result;
    }
    
    public byte[] EmbedSignature(byte[] publicCert, byte[] sign)
    {
        var chain = new List<Org.BouncyCastle.X509.X509Certificate>
        {
            Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(publicCert))
        };
        var signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
        using (var reader = new PdfReader(this.os))
        {
            using (var os2 = new MemoryStream())
            {
                signatureContainer.SetExternalDigest(sign, null, "RSA");
                byte[] encodedSignature = signatureContainer.GetEncodedPKCS7(this.hash, null, null, null, CryptoStandard.CMS);
                IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
                MakeSignature.SignDeferred(reader, "dsa", os2, external);
                return os2.ToArray();
            }
        }
    }
    

    我试图签署的pdf文件是 this 是的。

    添加签名字段后创建的临时pdf文件是 this 是的。

    签名的pdf文件是 this 是的。

    签名的哈希的base64格式为: klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=

    签名的base64格式为: Uam/J6W0YX99rVP4M9mL9Lg9l6YzC2yiR4OtJ18AH1PtBVaNPteT3oPS7SUc+6ak2LfijgJ6j1RgdLamodDPKl/0E90kbBenry+/g1Ttd1bpO8lqTn1PWJU2TxeGHwyRyaFBOUga2AxpErIHrwxfuKCBcodB7wvAqRjso0jovnyP/4DluyOPm97QWh4na0S+rtUWOdqVmKGOuGJ3sBXuk019ewpvFcqWBX4Mvz7IKV56wcxQVQuJLCiyXsMXoazwyDCvdteaDz05K25IVwgEEjwLrppnc/7Ue9a9KVadFFzXWXfia7ndmUCgyd70r/Z+Oviu9MIAZL8GuTpkD7fJeA==

    1 回复  |  直到 8 年前
        1
  •  2
  •   mkl    8 年前

    我在这里使用字节数组的十六进制编码。Base64编码的哈希

    klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
    

    十六进制编码等于

    92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
    

    简而言之

    您的代码对签名属性进行两次哈希运算。只是不要散列返回的字节 signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS) 在里面 GetHashOfPdf 但是使用经过身份验证的属性字节本身作为返回值。

    详细

    分析示例pdf中的签名

    • 实际上,签名属性的散列是

      92587a086a7b0d4cdacadeb612eb22aa3af504509c4d94f85dd827301abb41e6
      
    • 但是rsa中的散列加密了 DigestInfo 签名的对象是

      1DC7CAA50D88243327A9D928D5FB4F1A61CBEFF9E947D393DDA705BD61B67F25
      
    • 原来是前面提到的签名属性哈希的哈希。

    因此,你的

    var signature = signer.sign(toBeSignedHash);
    

    调用似乎散列 toBeSignedHash 再次重视。

    最简单的解决方法是替换

    byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
    result = DigestAlgorithms.Digest(new MemoryStream(signatureHash), "SHA256");
    

    通过

    result = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
    

    在里面 gethashofpdf 只有 signer.sign 做散列运算。

    分析这些问题

    在你的评论中

    你是怎么想出来的:)?

    好吧,您的问题不是第一个自定义itext签名过程导致错误或至少不需要的配置文件的问题。

    在分析这些问题的过程中,第一步通常是提取嵌入的签名容器并在ASN.1查看器中检查它。

    对于您的pdf,检查的主要结果是这样的签名看起来没有问题,并且您的签名属性不包含任何变量数据。

    如果在它们中有一些变量数据(例如签名时间属性),则导致问题的候选可能是,一旦显式地创建了两次签名的属性。 gethashofpdf ,一旦隐式地 EmbedSignature ,变量数据的值不同。但如上所述,情况并非如此。

    这里的下一步是实际检查所涉及的散列。检查文档散列很简单,可以计算有符号字节范围散列并与 MessageDigest 签名属性,参见 ExtractHash 测试 testSotnSignedpdf (在Java中)。

    你的pdf结果是好的。

    下面的步骤是更彻底地检查签名容器。在这种情况下,我曾经开始写一些支票,但没有走得很远。 SignatureAnalyzer 上课。我将它扩展为测试签名属性的哈希值,使用了您使用的签名算法,旧的。 RSASSA-PKCS1-v1_5 与许多其他签名算法相比,这一个允许简单地提取签名哈希。

    在这里,您的pdf结果是不正常的,签名属性的散列不同于签名散列。

    这里有两个常见的不匹配原因,

    • 签名的属性要么是用错误的编码签名的(它必须是常规的der编码,而不是任意的ber编码,特别是不是用签名中存储的值所具有的隐式标记进行的编码——甚至更大的参与者有时也会这样做,例如docusign,cf。 DSS-1343 )

    • 或者哈希在签名过程中被某种方式转换(例如哈希是Base64编码或再次散列)。

    事实证明,后者就是这里的情况,散列被再次散列。