C#PKCS7智能卡数字签名-文档自签名以来已被更改或损坏

时间:2018-06-25 20:37:07

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

我尝试使用智能卡(USB令牌)对pdf文件签名,但是在Adobe中打开签名的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==

1 个答案:

答案 0 :(得分:2)

我在这里使用字节数组的十六进制编码。您的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,检查的主要结果是这样的签名看起来还不错,并且您签名的属性中不包含任何可变数据。

如果其中包含一些可变数据(例如,签署时间属性),则可能会导致该问题的原因是您两次构建了签署属性,一次在{{ 1}}(一旦隐式位于GetHashOfPdf中,则变量数据具有不同的值)。但是如上所述,情况并非如此。

这里的下一步是实际检查涉及的哈希。检查文档哈希很简单,计算签名字节范围哈希并与EmbedSignature签名属性的值进行比较。 ExtractHash测试MessageDigest(在Java中)。

您的PDF结果证明还不错。

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

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

这里经常有两种不匹配的原因,

  • 已签名的属性中的任何一个都使用错误的编码进行签名(它必须是常规DER编码,而不是某些任意BER编码,尤其是不是带有存储在签名中的隐式标签的编码,其-甚至更大的玩家有时也会犯错,例如Docusign,比照DSS-1343

  • 或在签名过程中以某种方式对哈希进行了转换(例如,哈希经过base64编码或再次进行了哈希处理)。

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