乌拉圭ID卡PDF数字签名验证失败问题求助
使用乌拉圭身份证签名PDF时验证失败的问题
我是Mateo,来自乌拉圭,正在开发一款用乌拉圭带芯片身份证(智能卡)给PDF做数字签名的C#程序。项目用iText 7.2.5.0实现签名系统,BouncyCastle 1.8.9.0处理证书相关操作。程序是Windows窗体,点击按钮后读取指定目录的PDF并执行签名。
问题现象
- 第三方验证网站提示:文档自上次签名后已被修改
- Adobe Acrobat打开生成的PDF显示:至少存在一个无效签名
- 但用该第三方网站处理的PDF验证结果正常
签名流程及代码
签名分为三个步骤:生成文档哈希、生成预留签名位置的待签名PDF、导入签名生成最终文件。相关代码如下:
签名核心逻辑代码
using iText.Forms; using iText.Kernel.Geom; using iText.Kernel.Pdf; using iText.Signatures; using Org.BouncyCastle.Asn1.Ocsp; using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Text; using Aplicacion_CI.Models; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.Cms; using Org.BouncyCastle.Asn1.Nist; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.X509; using SysX509 = System.Security.Cryptography.X509Certificates; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.X509; using static iText.Signatures.PdfSigner; using Org.BouncyCastle.Bcpg.OpenPgp; using Org.BouncyCastle.Security; using System.Security.Policy; namespace Aplicacion_CI.Controllers { public class PreSignatureContainer : IExternalSignatureContainer { private PdfDictionary sigDic; private byte[] hash; public PreSignatureContainer(PdfName filter, PdfName subFilter) { sigDic = new PdfDictionary(); sigDic.Put(PdfName.Filter, filter); sigDic.Put(PdfName.SubFilter, subFilter); } public byte[] Sign(Stream data) { using (SHA256Managed sha256 = new SHA256Managed()) { hash = sha256.ComputeHash(data); } return new byte[0]; } public void ModifySigningDictionary(PdfDictionary signDic) { signDic.PutAll(sigDic); } public byte[] GetHash() { return hash; } } public class Sign { public static string NOMBRES = ""; private string PATH = "C:\\Aplicacion_CI\\PDF\\"; public byte[] signProcess(string signedHASH, string certificate) { byte[] signatureBytes = Utils.hexStringToByteArray(signedHASH); byte[] certificateBytes = Utils.hexStringToByteArray(certificate); X509Certificate x509Certificate = new X509CertificateParser().ReadCertificate(certificateBytes); SignerIdentifier sid = new SignerIdentifier(new IssuerAndSerialNumber(x509Certificate.IssuerDN, x509Certificate.SerialNumber)); AlgorithmIdentifier digAlgorithm = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256); Attributes authenticatedAttributes = null; AlgorithmIdentifier digEncryptionAlgorithm = new AlgorithmIdentifier(Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.Sha256WithRsaEncryption); Asn1OctetString encryptedDigest = new DerOctetString(signatureBytes); Attributes unauthenticatedAttributes = null; SignerInfo signerInfo = new SignerInfo(sid, digAlgorithm, authenticatedAttributes, digEncryptionAlgorithm, encryptedDigest, unauthenticatedAttributes); Asn1EncodableVector digestAlgs = new Asn1EncodableVector(signerInfo.DigestAlgorithm); Asn1Set digestAlgorithms = new DerSet(digestAlgs); ContentInfo contentInfo = new ContentInfo(CmsObjectIdentifiers.Data, null); Asn1EncodableVector certs = new Asn1EncodableVector(x509Certificate.CertificateStructure.ToAsn1Object()); Asn1Set certificates = new DerSet(certs); Asn1EncodableVector signerInfs = new Asn1EncodableVector(signerInfo); Asn1Set signerInfos = new DerSet(signerInfs); SignedData signedData = new SignedData(digestAlgorithms, contentInfo, certificates, null, signerInfos); contentInfo = new ContentInfo(CmsObjectIdentifiers.SignedData, signedData); return contentInfo.GetDerEncoded(); } public byte[] generateHash() { using (PdfReader reader = new PdfReader(PATH + "original.pdf")) using (FileStream fout = new FileStream(PATH + "prepared.pdf", FileMode.Create)) { StampingProperties sp = new StampingProperties(); sp.UseAppendMode(); PdfSigner pdfSigner = new PdfSigner(reader, fout, sp); pdfSigner.SetFieldName("Signature1"); PdfSignatureAppearance appearance = pdfSigner.GetSignatureAppearance(); appearance.SetPageNumber(1); PreSignatureContainer preSignatureContainer = new PreSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached); pdfSigner.SignExternalContainer(preSignatureContainer, 32000); byte[] documentHash = preSignatureContainer.GetHash(); return documentHash; } } public void signPDF(byte[] Signature) { PdfReader reader = new PdfReader(PATH + "prepared.pdf"); using (FileStream os = new FileStream(PATH + "signed.pdf", FileMode.Create)) { PdfSigner signer = new PdfSigner(reader, os, new StampingProperties()); PdfSigner.SignDeferred(signer.GetDocument(), "Signature1", os, new ExternalPrecalculatedSignatureContainer(Signature)); } reader.Close(); } } }
按钮触发代码
private void button_firma_Click(object sender, EventArgs e) { try { CI ci = Metodos.ciCompleta(); string datos = Response.Est(); Sign pdf = new Sign(); Sign.NOMBRES = ci.Nombres; byte[] pdf_hash = pdf.generateHash(); JObject sign_obj = Metodos.firma(BitConverter.ToString(pdf_hash).Replace("-", string.Empty).ToUpper(), ""); byte[] signature = pdf.signProcess(Convert.ToString(sign_obj["hashSignature"]), Convert.ToString(sign_obj["certificateFirma"])); if (detectarEstadoSinFoto()) { pdf.signPDF(signature); } } catch (Exception ex) { MessageBox.Show(ex.ToString()); } }
问题分析及修复建议
1. 预签名哈希计算逻辑错误
PreSignatureContainer的Sign方法直接计算输入流的SHA256哈希,但iText传入的并非整个原始文档数据,而是待签名的文档字节范围数据。正确做法是:
- 不要自行计算哈希,改用
IExternalSignature接口,让iText处理文档字节范围的哈希流程,你只需对iText提供的待签名摘要进行签名。
2. CMS签名结构缺少关键认证属性
对于Adbe_pkcs7_detached类型的签名,Adobe等验证器要求必须包含认证属性(Authenticated Attributes),其中必须包含contentType、signingTime、messageDigest三个属性,否则会被判定为无效。修复signProcess方法:
// 构建认证属性 Asn1EncodableVector authenticatedAttributesVector = new Asn1EncodableVector(); // 添加contentType属性 authenticatedAttributesVector.Add(new Attribute(CmsAttributes.ContentType, new DerSet(CmsObjectIdentifiers.Data))); // 添加signingTime属性 authenticatedAttributesVector.Add(new Attribute(CmsAttributes.SigningTime, new DerSet(new DerUtcTime(DateTime.UtcNow)))); // 添加messageDigest属性(文档哈希) authenticatedAttributesVector.Add(new Attribute(CmsAttributes.MessageDigest, new DerSet(new DerOctetString(pdf_hash)))); Attributes authenticatedAttributes = new Attributes(new DerSet(authenticatedAttributesVector)); // 创建SignerInfo时传入认证属性 SignerInfo signerInfo = new SignerInfo(sid, digAlgorithm, authenticatedAttributes, digEncryptionAlgorithm, encryptedDigest, unauthenticatedAttributes);
3. 延迟签名实现错误
signPDF方法中重新创建PdfSigner实例是错误的,正确的延迟签名应该直接读取待签名PDF并调用SignDeferred:
public void signPDF(byte[] Signature) { using (PdfReader reader = new PdfReader(PATH + "prepared.pdf")) using (FileStream fout = new FileStream(PATH + "signed.pdf", FileMode.Create)) { PdfDocument document = new PdfDocument(reader); PdfSigner.SignDeferred(document, "Signature1", fout, new ExternalPrecalculatedSignatureContainer(Signature)); } }
4. 哈希传递格式验证
确认智能卡接口对哈希字符串的格式要求(大小写、分隔符等),当前BitConverter.ToString(pdf_hash).Replace("-", string.Empty).ToUpper()的转换是否符合接口规范。
额外建议
- 启用iText日志功能,查看签名过程中的详细错误信息
- 对比第三方网站生成的PDF签名结构,重点检查认证属性、算法OID等字段差异
- 使用iText提供的
MakeSignature工具类简化签名流程,减少手动构建CMS结构的出错概率
内容的提问来源于stack exchange,提问作者Mateo
相关产品推荐
相关产品推荐

