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

使用iTextSharp签名PDF时自定义时间导致签名无效的问题排查

iTextSharp PDF签名自定义时区时间导致签名无效的解决办法

我有一个基于iTextSharp的PDF电子签名工具,服务器部署在美国北部:

  • 使用默认签名时间时,生成的PDF签名有效;
  • 自定义设置时区转换后的签名时间后,生成的PDF签名无效。

可正常工作的代码

public void Sign(string DSCFileNameWithPath, string keyPassword, string SoruceDocument, string DesitinationDocument, Cert mycert, string SigReason, string SigContact, string SigLocation, string AppprovedBy, bool visible)
{
    string logfile = Path.GetDirectoryName(SoruceDocument) + "\\WriteText.txt";

    try
    {
        PdfReader reader = new PdfReader(SoruceDocument);
        PdfStamper st = PdfStamper.CreateSignature(reader, new FileStream(DesitinationDocument, FileMode.Create, FileAccess.Write), '\0', null, true);
        System.IO.File.WriteAllText(logfile, "st created" + "\n");
        st.MoreInfo = HashtableToDictionary(metadata.getMetaData());
        st.XmpMetadata = metadata.getStreamedMetaData();
        PdfSignatureAppearance sap = st.SignatureAppearance;

        System.IO.File.AppendAllText(logfile, "DSCFileNameWithPath" + DSCFileNameWithPath + "\n");
        System.IO.File.AppendAllText(logfile, "SoruceDocument" + SoruceDocument + "\n");

        System.IO.File.AppendAllText(logfile, "Cert Initialization " + "\n");

        X509Certificate2 cert = new X509Certificate2(DSCFileNameWithPath, keyPassword);
        IExternalSignature externalSignature = new X509Certificate2Signature(cert, "SHA-1");

        System.IO.File.AppendAllText(logfile, "Cert reading started " + "\n");

        Stream fs = File.OpenRead(DSCFileNameWithPath);
        Stream privateKeyStream = fs;

        System.IO.File.AppendAllText(logfile, "Cert reading finished " + "\n");

        Pkcs12Store pk12 = new Pkcs12Store(privateKeyStream, keyPassword.ToCharArray());
        privateKeyStream.Dispose();

        string alias = null;
        foreach (string tAlias in pk12.Aliases)
        {
            if (pk12.IsKeyEntry(tAlias))
            {
                alias = tAlias; break;
            }
        }
        System.IO.File.AppendAllText(logfile, "pk12 finished " + "\n");

        var pk = pk12.GetKey(alias).Key;

        File.AppendAllText(logfile, "Pk ceated.");

        X509CertificateEntry[] ce = pk12.GetCertificateChain(alias);
        Org.BouncyCastle.X509.X509Certificate[] chain;
        chain = new Org.BouncyCastle.X509.X509Certificate[ce.Length];
        for (int k = 0; k < ce.Length; ++k)
        {
            chain[k] = ce[k].Certificate;
        }
        sap.Reason = SigReason;
        sap.Contact = AppprovedBy;
        sap.Location = SigLocation;
        File.AppendAllText(logfile, "Got certifcate.");
        File.AppendAllText(logfile, "Singer started .");
        sap.Acro6Layers = false;
        sap.Layer4Text = PdfSignatureAppearance.questionMark;
        sap.SetVisibleSignature(new iTextSharp.text.Rectangle(580, 100, 450, 150), 1, null);
        MakeSignature.SignDetached(sap, externalSignature, chain, null, null, null, 0, CryptoStandard.CMS);
        st.Close();
        File.AppendAllText(logfile, "Sign finished.");
    }
    catch (Exception ex)
    {
        File.AppendAllText(logfile, "Error occured" + ex + ex.Message + ex.InnerException + ex.StackTrace);
    }
}

无法正常工作的代码

public void Sign(string DSCFileNameWithPath, string keyPassword, string SoruceDocument, string DesitinationDocument, Cert mycert, string SigReason, string SigContact, string SigLocation, string AppprovedBy, bool visible)
{
    string logfile = Path.GetDirectoryName(SoruceDocument) + "\\WriteText.txt";

    try
    {
        PdfReader reader = new PdfReader(SoruceDocument);
        PdfStamper st = PdfStamper.CreateSignature(reader, new FileStream(DesitinationDocument, FileMode.Create, FileAccess.Write), '\0', null, true);
        System.IO.File.WriteAllText(logfile, "st created" + "\n");
        st.MoreInfo = HashtableToDictionary(metadata.getMetaData());
        st.XmpMetadata = metadata.getStreamedMetaData();
        PdfSignatureAppearance sap = st.SignatureAppearance;

        System.IO.File.AppendAllText(logfile, "DSCFileNameWithPath" + DSCFileNameWithPath + "\n");
        System.IO.File.AppendAllText(logfile, "SoruceDocument" + SoruceDocument + "\n");

        System.IO.File.AppendAllText(logfile, "Cert Initialization " + "\n");

        X509Certificate2 cert = new X509Certificate2(DSCFileNameWithPath, keyPassword);
        IExternalSignature externalSignature = new X509Certificate2Signature(cert, "SHA-1");

        System.IO.File.AppendAllText(logfile, "Cert reading started " + "\n");

        Stream fs = File.OpenRead(DSCFileNameWithPath);
        Stream privateKeyStream = fs;

        System.IO.File.AppendAllText(logfile, "Cert reading finished " + "\n");

        Pkcs12Store pk12 = new Pkcs12Store(privateKeyStream, keyPassword.ToCharArray());
        privateKeyStream.Dispose();

        string alias = null;
        foreach (string tAlias in pk12.Aliases)
        {
            if (pk12.IsKeyEntry(tAlias))
            {
                alias = tAlias; break;
            }
        }
        System.IO.File.AppendAllText(logfile, "pk12 finished " + "\n");

        var pk = pk12.GetKey(alias).Key;

        File.AppendAllText(logfile, "Pk ceated.");

        X509CertificateEntry[] ce = pk12.GetCertificateChain(alias);
        Org.BouncyCastle.X509.X509Certificate[] chain;
        chain = new Org.BouncyCastle.X509.X509Certificate[ce.Length];
        for (int k = 0; k < ce.Length; ++k)
        {
            chain[k] = ce[k].Certificate;
        }
        sap.SignDate = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.FindSystemTimeZoneById(A_Common_Objects.TimeZone));
        sap.Reason = SigReason;
        sap.Contact = AppprovedBy;
        sap.Location = SigLocation;
        File.AppendAllText(logfile, "Got certifcate.");
        File.AppendAllText(logfile, "Singer started .");
        sap.Acro6Layers = false;
        sap.Layer4Text = PdfSignatureAppearance.questionMark;
        sap.SetVisibleSignature(new iTextSharp.text.Rectangle(580, 100, 450, 150), 1, null);
        MakeSignature.SignDetached(sap, externalSignature, chain, null, null, null, 0, CryptoStandard.CMS);
        st.Close();
        File.AppendAllText(logfile, "Sign finished.");
    }
    catch (Exception ex)
    {
        File.AppendAllText(logfile, "Error occured" + ex + ex.Message + ex.InnerException + ex.StackTrace);
    }
}

代码差异

仅添加了一行设置签名时间的代码:

sap.SignDate = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.FindSystemTimeZoneById(A_Common_Objects.TimeZone));

解决办法

问题根源是iTextSharp处理带时区信息的DateTime对象时,生成的签名时间格式不符合PDF/PKI规范,导致签名验证失败。可以通过两种方式修改:

方案1:将转换后的时间转为无时区标记的DateTime

把设置SignDate的代码替换为:

DateTime targetLocalTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.FindSystemTimeZoneById(A_Common_Objects.TimeZone));
// 移除时区标记,转为Unspecified类型
sap.SignDate = DateTime.SpecifyKind(targetLocalTime, DateTimeKind.Unspecified);

这样iTextSharp会将该时间视为本地时间写入签名,不会携带时区信息,避免格式冲突。

方案2:底层用UTC时间保证有效性,自定义可见签名显示文本

如果只是需要用户在PDF里看到目标时区的时间,底层签名仍用默认UTC时间(确保有效性),可以单独设置可见签名的显示文本:

// 保留默认的UTC签名时间(不设置SignDate)
// 自定义可见签名区域的显示文本,显示目标时区时间
sap.Layer2Text = $"Signed on: {TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.FindSystemTimeZoneById(A_Common_Objects.TimeZone)):yyyy-MM-dd HH:mm:ss}";

原理说明

PDF签名的有效性依赖于符合标准的时间戳,通常推荐使用UTC时间避免时区歧义。直接设置带DateTimeKind.Local或DateTimeKind.Utc之外的时区标记,会让iTextSharp生成不符合规范的时间格式,导致验证工具无法识别,进而判定签名无效。两种方案分别从修正时间格式、分离底层时间与显示时间的角度解决问题。


内容的提问来源于stack exchange,提问作者SURAJKUMAR PATIL

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 03:40:38