我已经使用 iTextSharp 5.5.7 一段时间了,找不到从智能卡为 PDF 进行有效数字签名的正确方法 - Adobe Reader 总是说其签名者未知,无法解码签名” DER 数据。
我查看了 MakeSignature.cs 代码以供参考,其作用是:
Stream data = signatureAppearance.GetRangeStream();
// gets the first hash
byte[] hash = DigestAlgorithms.Digest(data, hashAlgorithm);
// gets the second hash or is it not a hash at all ?
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, crlBytes, sigtype);
中的“sign”方法
“@param message 您想要进行哈希和签名的消息”
// looks like externalSignature.Sign() should make another hash out of "sh"
// and use this hash to compute a signature
byte[] extSignature = externalSignature.Sign(sh);
所以我理解的签名流程如下:
当我使用 Adobe Reader 签署 PDF 时,在第 6 步中,第三个哈希值的长度为 32 字节。 从智能卡的角度来看,我使用 Acrobat 和 iText 执行相同的步骤,但使用 iText 时签名无效,可能出了什么问题?
我使用的代码:
public void StartTest(){
X509Certificate2 cert = new X509Certificate2();
cert.Import("cert.cer"); // certificate obtained from smart card
X509CertificateParser certParse = new Org.BouncyCastle.X509.X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { certParse.ReadCertificate(cert.RawData) };
// Reader and stamper
PdfReader pdfReader = new PdfReader("original.pdf");
Stream signedPdf = new FileStream("signed.pdf", FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(pdfReader, signedPdf, '\0', null, false);
// Appearance
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SignatureCreator = "Me";
appearance.Reason = "Testing iText";
appearance.Location = "On my Laptop";
appearance.SignatureGraphic = Image.GetInstance("img.png"); // visual image
appearance.SetVisibleSignature(new Rectangle(50, 50, 250, 100), pdfReader.NumberOfPages, "Signature");
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION;
// Timestamp
TSAClientBouncyCastle tsc = new TSAClientBouncyCastle("http://ts.cartaodecidadao.pt/tsa/server", "", "");
// Digital signature
IExternalSignature externalSignature = new MyExternalSignature2("SHA-1");
MyMakeSignature.SignDetached(appearance, externalSignature, chain, null, null, tsc, 0, CryptoStandard.CADES);
stamper.Close();
}
外部签名实现(类MyExternalSignature2):
class MyExternalSignature2 : IExternalSignature
{
private String hashAlgorithm;
private String encryptionAlgorithm;
public MyExternalSignature2(String hashAlgorithm)
{
this.encryptionAlgorithm = "RSA";
this.hashAlgorithm = DigestAlgorithms.GetDigest(DigestAlgorithms.GetAllowedDigests(hashAlgorithm));
}
public virtual byte[] Sign(byte[] message) {
byte[] hash = null;
using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
{
hash = sha1.ComputeHash(message);
}
byte[] sig = MySC.GetSignature(hash);
return sig;
}
public virtual String GetHashAlgorithm() {
return hashAlgorithm;
}
public virtual String GetEncryptionAlgorithm() {
return encryptionAlgorithm;
}
}
OP 提供了许多签名文件样本,对它们进行分析后发现,我最初的答案显然是基于误解。
OP 提供了三个由他的代码签名的 PDF,首先是这两个:
检查它们后,iText CAdES 使用 SHA1 签名的特殊性就显现出来了:对于
CryptoStandard.CADES
,iText 即使对于 SHA1 也使用 SigningCertificateV2
属性,但规范建议在那里使用 SigningCertificate
属性。为了防止这种特性的干扰,OP提供了第三个文件
但事实证明,这个怪癖并不是OP观察的原因,Adobe Reader仍然报告密码学库错误。
因此,回到分析。
由于签名算法为 SHA1withRSA/2048 和 SHA256withRSA/2048,因此可以使用相应证书中的公钥简单地解密内部签名值。
对于 ex_signed.pdf 成功,但对于 ex_signed_2.pdf 或 ex_signed_3.pdf 失败。
OP同时在评论中指出:
这些文件之间的区别是用于签名的证书,在第一个文件中,我使用证书进行身份验证,这是(我认为)可以接受的,但我有另一个专门用于数字签名的证书,我在第二个和第三个文件中使用了它。
因此,我尝试使用第一个文件中的证书来解码第二个和第三个文件中的签名值,事实上,这有效!因此,第二个和第三个文件声称是由与该替代证书关联的私钥签名的,但实际上是使用与前一个证书关联的私钥签名的。
因此: 问题 1:文件 2 和 3 中的签名使用错误的私钥/智能卡上的错误应用程序进行签名。
为了进一步分析问题,我查看了使用身份验证证书成功解码的签名值:
ex_signed.pdf:
2a8945abe450b2c1cd232249b8f811d352ad0d29
ex_signed_2.pdf
cc24acc848002df63733941e34437f8aef1c746c
ex_signed_3.pdf
45f8e451f8b9f39f0c1f59eea8b6308fba22176ac62ebd14bbf07e5407aed7e8
因此,对于 SHA1,有 20 个字节,对于 SHA-256,有 32 个字节。这正是哈希值的大小,因此很可能这些只是裸哈希值。
但是,这是错误的,XXXwithRSA 签名应包含一个加密结构,其中包含哈希算法的 OID 和哈希值(采用 ASN.1 表示法):
DigestInfo ::= SEQUENCE {
digestAlgorithm DigestAlgorithmIdentifier,
digest Digest
}
DigestAlgorithmIdentifier ::= AlgorithmIdentifier
Digest ::= OCTET STRING
对于背景参见。 RFC 3447.
这解释了OP的观察:
第一个错误被描述为“BER解码错误”,
验证者尝试将裸哈希解释为使用 BER 编码的 ASN.1 序列,但这显然失败了。
因此: 问题 2:智能卡加密裸哈希值,但必须加密封装该哈希的
DigestInfo
对象。
如果您的
MySC.GetSignature
在签署数据时调用哈希(并且不希望数据之前已经经过哈希处理),则应替换
public virtual byte[] Sign(byte[] message)
{
byte[] hash = null;
using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
{
hash = sha1.ComputeHash(message);
}
byte[] sig = MySC.GetSignature(hash);
return sig;
}
通过类似
public virtual byte[] Sign(byte[] message)
{
byte[] sig = MySC.GetSignature(message);
return sig;
}
嗨,我也有类似的问题,我的服务器只接受string格式的哈希值,但我的签名方法存在于externalsignature中,哈希值是Byte[],你@mkl可以帮我吗这里