C# PKCS7 智能卡数字签名 - 文档自签名后已被更改或损坏
C# PKCS7 Smartcard Digital Signature - Document has been altered or corrupted since it was signed
我尝试使用智能卡(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, '[=10=]', 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==
我这里使用字节数组的十六进制编码。您的 base64 编码哈希
klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
十六进制编码等于
92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
###简而言之
您的代码对签名属性进行了两次哈希处理。简单地不要在 GetHashOfPdf
中对由 signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS)
编辑的字节 return 进行哈希处理,而是使用经过身份验证的属性字节本身作为 return 值。
###详细
分析您示例 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
进行散列。
###分析此类问题
在您提问的评论中
how did you figure all this out :)?
好吧,你的问题不是第一个问题,自定义的 iText 签名过程会导致错误或至少是不需要的配置文件。
在分析这些问题的过程中,第一步通常是提取嵌入的签名容器并在 ASN.1 查看器中检查它。
对于您的 PDF,检查的主要结果是签名本身看起来没问题,并且您的签名属性不包含任何可变数据。
如果其中有一些可变数据(例如签名时间属性),则问题的原因可能是您构建了两次签名属性,一次显式地出现在 GetHashOfPdf
中,一次隐式地出现在 EmbedSignature
中,变量数据具有不同的值。但正如上文所述,事实并非如此。
这里的下一步是实际检查涉及的哈希值。检查文档哈希很简单,计算带符号的字节范围哈希并与 MessageDigest
签名属性的值进行比较,参见。 ExtractHash 测试 testSotnSignedpdf
(在 Java 中)。
你的 PDF 结果是好的。
下一步是更彻底地检查签名容器。在这种情况下,我曾经开始写一些支票,但并没有走得太远,参见。 SignatureAnalyzer class. I extended it a bit for the test of the hash of the signed attributes making use of the signature algorithm you used, the old RSASSA-PKCS1-v1_5:与许多其他签名算法相比,这个算法允许简单地提取已签名的哈希值。
您的 PDF 的结果结果不正确,签名属性的哈希值与签名哈希值不同。
这里有两个常见的不匹配原因,
要么签名的属性是用错误的编码签名的(它必须是常规的 DER 编码,而不是一些任意的 BER 编码,特别是不是带有隐式标记的编码存储在签名中的值有 --- 甚至更大的玩家有时也会犯这个错误,例如 Docusign,cf. DSS-1343)
或者哈希在签名期间以某种方式进行了转换(例如,哈希是 base64 编码或再次哈希)。
原来这里是后者,又哈希了一遍
我尝试使用智能卡(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, '[=10=]', 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==
我这里使用字节数组的十六进制编码。您的 base64 编码哈希
klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
十六进制编码等于
92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
###简而言之
您的代码对签名属性进行了两次哈希处理。简单地不要在 GetHashOfPdf
中对由 signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS)
编辑的字节 return 进行哈希处理,而是使用经过身份验证的属性字节本身作为 return 值。
###详细
分析您示例 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
进行散列。
###分析此类问题
在您提问的评论中
how did you figure all this out :)?
好吧,你的问题不是第一个问题,自定义的 iText 签名过程会导致错误或至少是不需要的配置文件。
在分析这些问题的过程中,第一步通常是提取嵌入的签名容器并在 ASN.1 查看器中检查它。
对于您的 PDF,检查的主要结果是签名本身看起来没问题,并且您的签名属性不包含任何可变数据。
如果其中有一些可变数据(例如签名时间属性),则问题的原因可能是您构建了两次签名属性,一次显式地出现在 GetHashOfPdf
中,一次隐式地出现在 EmbedSignature
中,变量数据具有不同的值。但正如上文所述,事实并非如此。
这里的下一步是实际检查涉及的哈希值。检查文档哈希很简单,计算带符号的字节范围哈希并与 MessageDigest
签名属性的值进行比较,参见。 ExtractHash 测试 testSotnSignedpdf
(在 Java 中)。
你的 PDF 结果是好的。
下一步是更彻底地检查签名容器。在这种情况下,我曾经开始写一些支票,但并没有走得太远,参见。 SignatureAnalyzer class. I extended it a bit for the test of the hash of the signed attributes making use of the signature algorithm you used, the old RSASSA-PKCS1-v1_5:与许多其他签名算法相比,这个算法允许简单地提取已签名的哈希值。
您的 PDF 的结果结果不正确,签名属性的哈希值与签名哈希值不同。
这里有两个常见的不匹配原因,
要么签名的属性是用错误的编码签名的(它必须是常规的 DER 编码,而不是一些任意的 BER 编码,特别是不是带有隐式标记的编码存储在签名中的值有 --- 甚至更大的玩家有时也会犯这个错误,例如 Docusign,cf. DSS-1343)
或者哈希在签名期间以某种方式进行了转换(例如,哈希是 base64 编码或再次哈希)。
原来这里是后者,又哈希了一遍