我已经玩过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);
然后,根据"签署" IExternalSignature.cs 中的方法
" @param消息您想要进行哈希并签名"
// 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;
}
}
答案 0 :(得分:0)
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同时在评论中指出:这些文件之间的差异是用于签名的证书,在第一个我使用证书进行身份验证,这是(我认为)可以接受,但我有另一个专门用于数字签名的证书,我在第2和第3个文件中使用
所以我尝试使用第一个文件中的证书解码第二个和第三个文件中的签名值,实际上,这有效!因此,第二个和第三个文件声称由与该备用证书关联的私钥签名,但实际上是使用与前一个证书关联的私钥进行签名。
因此:问题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
对于背景cf. 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;
}