You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

乌拉圭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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 15:57:25