【问题标题】:C# PKCS7 Smartcard Digital Signature - Document has been altered or corrupted since it was signedC# PKCS7 智能卡数字签名 - 自签名以来文档已被更改或损坏
【发布时间】:2018-12-04 11:59:42
【问题描述】:

我尝试使用智能卡(USB 令牌)对 pdf 文件进行签名,但在 Adob​​e 中打开已签名的 pdf 文件时遇到 "Document has been altered or corrupted since it was signed" 错误。该错误不是那么具有描述性,我不确定在哪里看,因为代码对我来说似乎不错,但显然不是......

我使用的代码是:

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==

【问题讨论】:

    标签: c# pdf itext digital-signature pkcs#7


    【解决方案1】:

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

    klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
    

    十六进制编码等于

    92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
    

    ###总之

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

    ###详细

    分析您的示例 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,检查的主要结果是签名本身看起来没问题,并且您的签名属性不包含任何可变数据。

    如果其中有一些可变数据(例如签名时间属性),问题的原因可能是您构建了两次签名属性,一次在 @987654338 中明确@,曾经隐含在 EmbedSignature 中,变量数据具有不同的值。但如上所述,情况并非如此。

    这里的下一步是实际检查所涉及的哈希值。检查文档散列很简单,计算带符号字节范围散列并与MessageDigest 带符号属性的值进行比较,参见。 ExtractHash 测试 testSotnSignedpdf(在 Java 中)。

    你的 PDF 的结果是好的。

    接下来的步骤是更彻底地检查签名容器。在这种情况下,我曾经开始写一些支票,但没有走得太远,参见。 SignatureAnalyzer 类。我使用您使用的签名算法,旧的RSASSA-PKCS1-v1_5 对其进行了扩展,以测试签名属性的哈希值:与许多其他签名算法相比,这个算法可以轻松提取签名哈希值。

    您的 PDF 结果不正确,签名属性的哈希值与签名哈希值不同。

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

    • 签名的属性使用错误的编码进行签名(它必须是常规的 DER 编码,而不是一些任意的 BER 编码,尤其不是带有隐式标记的编码,签名中存储的值具有 --- 甚至更大的播放器有时会做错,例如Docusign,参见DSS-1343)

    • 或哈希在签名期间以某种方式进行了转换(例如,哈希被 base64 编码或再次哈希)。

    事实证明,这里是后者,哈希又被哈希了。

    【讨论】:

    • 成功了,谢谢你百万次。不过有一个问题,你是怎么弄明白这一切的:)?我的意思是我在其他几个问题中也看到了您的解决方案,但无法弄清楚您解决这些签名问题的方法。调试/跟踪此类问题的任何提示?
    • 哇,你的工作真专业。我也会在有时间的时候检查回购。保重。
    猜你喜欢
    • 1970-01-01
    • 2023-04-03
    • 2021-06-20
    • 2016-09-16
    • 1970-01-01
    • 2021-05-11
    • 2021-11-06
    • 2021-02-22
    • 1970-01-01
    相关资源
    最近更新 更多