使用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
相关产品推荐
相关产品推荐

