使用iTextSharp通过SHA1签名PDF失败,USB签名未嵌入求助
我仔细检查了你的代码,发现几个关键问题导致签名无法正确嵌入PDF,即使程序运行无报错。下面是具体的问题分析和修复方案:
1. 证书私钥未正确加载
在selectCert方法中,你创建了带密码的newCert但没有把它赋值给最终返回的cert变量。从系统证书存储中获取的证书通常不包含可直接使用的私钥,必须通过带密码重新加载证书才能获取可用的私钥。这会导致后续签名时使用的私钥无效,生成的签名自然无法被PDF验证。
修复方式:
把var newCert = new X509Certificate2(certificate.RawData, "mypassword");改成certificate = new X509Certificate2(certificate.RawData, "mypassword");,确保返回的证书包含可用的私钥。
2. 待签名哈希计算错误
你直接对appearance.GetRangeStream()计算SHA1哈希,但在调用MakeSignature.SignExternalContainer后,PdfStamper已经完成了临时PDF的生成,此时appearance.GetRangeStream返回的流已经不是正确的待签名范围了。正确的做法是从生成的临时PDF中读取指定的签名字节范围,再计算哈希。
修复方式:
替换原来的哈希计算代码,改为从临时文件中提取正确的待签字节:
// 替换原来的array = SHA1.Create().ComputeHash(appearance.GetRangeStream()); using (var reader = new PdfReader(tempPdf)) { var sigDic = reader.AcroFields.GetSignatureDictionary(signatureFieldName); var range = sigDic.GetAsArray(PdfName.BYTERANGE); using (var fs = new FileStream(tempPdf, FileMode.Open, FileAccess.Read)) { var toSign = new byte[range[2].AsInt()]; fs.Position = range[1].AsInt(); fs.Read(toSign, 0, toSign.Length); array = SHA1.Create().ComputeHash(toSign); } }
3. 自定义签名容器未配置必要属性
你的MyExternalSignatureContainer类的ModifySigningDictionary方法是空的,这会导致PDF文件无法识别签名的算法和格式,即使嵌入了签名字节,也不会被认定为有效签名。
修复方式:
完善ModifySigningDictionary方法,添加签名所需的关键配置:
public void ModifySigningDictionary(PdfDictionary signDic) { signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKLITE); signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED); signDic.Put(PdfName.DIGESTALGORITHM, new PdfName("SHA1")); }
4. 手动释放资源导致的流异常
你在using语句块内手动调用stamper.Dispose()、stamper.Close()等方法,而using语句本身会自动处理资源释放,手动调用可能导致流提前关闭,破坏临时PDF文件的完整性。
修复方式:
删除所有手动调用的Dispose()和Close()方法,让using自动管理资源生命周期。
完整修复后的代码
private void Form1_Load(object sender, EventArgs e) { string UnsingnedPdf = @"E:\PrintContractAdviceNote.pdf"; string TempPdf = @"E:\PrintContractAdviceNote_TMP.pdf"; string singnedPdf = @"E:\PrintContractAdviceNote_Sign.pdf"; PDFSigning cl = new PDFSigning(); cl.GetBytesToSign(UnsingnedPdf, TempPdf, singnedPdf, "CHUKY1", 3, "BIG BEAR"); } public static X509Certificate2 selectCert() { X509Certificate2 certificate = null; var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); try { store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); if (store.Certificates.Count == 1) { certificate = store.Certificates[0]; } else if (store.Certificates.Count > 0) { try { var certificates = X509Certificate2UI.SelectFromCollection(store.Certificates, "Digital Certificates", "Select a certificate from the following list:", X509SelectionFlag.SingleSelection); if (certificates.Count == 1) { certificate = certificates[0]; // 修复:将带密码的证书赋值给返回变量 certificate = new X509Certificate2(certificate.RawData, "mypassword"); } else { throw new ArgumentException("Please select a certificate to publish PnL to Flash"); } } catch (Exception ex) { Console.WriteLine($"Certificate selection error: {ex.Message}"); } } else { throw new ArgumentException("There is no certificate available to publish PnL to flash, please contact support."); } } finally { store.Close(); } return certificate; } public class IpInfo { public string Country { get; set; } } public byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signedPdf, string signatureFieldName, Int16 iType, string SignName) { if (File.Exists(tempPdf)) File.Delete(tempPdf); if (File.Exists(signedPdf)) File.Delete(signedPdf); byte[] array = null; // 使用using自动管理资源,无需手动Close/Dispose using (var os = File.OpenWrite(tempPdf)) using (var reader = new PdfReader(unsignedPdf)) using (var stamper = PdfStamper.CreateSignature(reader, os, '\0')) { PdfSignatureAppearance appearance = stamper.SignatureAppearance; appearance.Contact = "www.marico.com"; appearance.SignDate = DateTime.Now; appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS; appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION; appearance.Acro6Layers = true; appearance.Layer2Text = $"Signed by:\n {SignName}\nDate :\n {DateTime.Now.ToShortDateString()} {DateTime.Now.ToShortTimeString()}"; Image img = Image.GetInstance(@"E:\Tài liệu\Marico\HINH ANH\talent.png"); if (iType == 3) { appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(15, 50, 235, 95), reader.NumberOfPages, signatureFieldName); } else { appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(350, 50, 570, 95), reader.NumberOfPages, signatureFieldName); } appearance.SignatureGraphic = img; IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED); MakeSignature.SignExternalContainer(appearance, external, 8192); } // 修复:从临时PDF中读取正确的待签字节范围并计算哈希 using (var reader = new PdfReader(tempPdf)) { var sigDic = reader.AcroFields.GetSignatureDictionary(signatureFieldName); var range = sigDic.GetAsArray(PdfName.BYTERANGE); using (var fs = new FileStream(tempPdf, FileMode.Open, FileAccess.Read)) { var toSign = new byte[range[2].AsInt()]; fs.Position = range[1].AsInt(); fs.Read(toSign, 0, toSign.Length); array = SHA1.Create().ComputeHash(toSign); } } X509Certificate2 cert = selectCert(); if (cert?.PrivateKey == null) { throw new InvalidOperationException("Failed to retrieve valid private key from selected certificate"); } RSAPKCS1SignatureFormatter formatter = new RSAPKCS1SignatureFormatter(cert.PrivateKey); formatter.SetHashAlgorithm("SHA1"); byte[] signature = formatter.CreateSignature(array); EmbedSignature(tempPdf, signedPdf, signatureFieldName, signature); return array; } public static bool IsFileLocked(string pathfile) { try { using (FileStream fs = new FileStream(pathfile, FileMode.Open)) { // using自动处理流关闭 } return false; } catch (IOException) { return true; } } private static void SetSigPosition(PdfSignatureAppearance sigAppearance, int oldSigCount, string signatureFieldName, int numpage = 1) { float llx = (200 + 20) * (oldSigCount % 5) + 20, lly = (25 + 20) * (oldSigCount / 5) + 5, urx = llx + 200, ury = lly + 45; } public void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, byte[] signedBytes) { IExternalSignatureContainer external = new MyExternalSignatureContainer(signedBytes); using (var os = File.OpenWrite(signedPdf)) using (var reader = new PdfReader(tempPdf)) { MakeSignature.SignDeferred(reader, signatureFieldName, os, external); } } private class MyExternalSignatureContainer : IExternalSignatureContainer { private readonly byte[] signedBytes; public MyExternalSignatureContainer(byte[] signedBytes) { this.signedBytes = signedBytes; } public byte[] Sign(Stream data) { return signedBytes; } // 修复:添加签名字典配置 public void ModifySigningDictionary(PdfDictionary signDic) { signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKLITE); signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED); signDic.Put(PdfName.DIGESTALGORITHM, new PdfName("SHA1")); } }
内容的提问来源于stack exchange,提问作者Vinh Lê Quốc

