使用iText 7分两步签名PDF后文件损坏,求排查问题
PDF数字签名文件损坏问题排查与修复
核心问题分析
你用iText7分两步签名PDF后文件损坏,主要是以下3个原因:
- 签名方式错误:第一步生成的临时文件已包含签名占位,第二步却重新调用
SignExternalContainer从头签名,而非替换已有占位,直接破坏了PDF文件结构。 - 参数传递错误:
SignedSignatureContainer中调用GetEncodedPKCS7时,传入的是认证属性字节,而非第一步计算的原始文档哈希,导致签名结构无效。 - 文件流处理不当:第一步用
FileMode.OpenOrCreate可能残留旧文件数据,第二步用File.OpenWrite会在目标文件已存在时追加内容,引发结构混乱。
修正方案
1. 修正第一步临时文件生成逻辑
确保临时文件全新创建,避免旧数据干扰:
public static byte[] ComputeHash(string source, string temp, X509Certificate[] chains, string reason, string location, int qtySigns, int pageNumber) { IList<ICrlClient> crlList = new List<ICrlClient>(); crlList.Add(new CrlClientOnline(chains)); using (var reader = new PdfReader(source)) { // 改用FileMode.Create确保生成全新文件 using (var os = new FileStream(temp, FileMode.Create, FileAccess.Write)) { var signer = new PdfSigner(reader, os, new StampingProperties().UseAppendMode()); var signatureAppearance = signer.GetSignatureAppearance(); signatureAppearance .SetReason(reason) .SetLocation(location) .SetPageNumber(pageNumber) .SetRenderingMode(PdfSignatureAppearance.RenderingMode.NAME_AND_DESCRIPTION) .SetCertificate(chains[0]) .SetPageRect(new iText.Kernel.Geom.Rectangle(36, 20, 144, 53)); var container = new EmptySignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached); signer.SignExternalContainer(container, 8192); return container.Hash; } } }
2. 重写第二步签名方法,使用替换签名模式
替换第一步生成的空白签名占位,而非重新签名:
public static void Sign(string tempFile, string targetFile, X509Certificate[] chains, byte[] originalDocHash, byte[] signedAttrHash, string digestAlgorithm = "SHA256") { using (PdfReader reader = new PdfReader(tempFile)) { // 用FileMode.Create生成全新输出文件 using (FileStream outStream = new FileStream(targetFile, FileMode.Create, FileAccess.Write)) { // 获取临时文件中的签名字段名 SignatureUtil signatureUtil = new SignatureUtil(reader); IList<string> signNames = signatureUtil.GetSignatureNames(); if (signNames.Count == 0) throw new InvalidOperationException("临时文件中无签名占位"); string signName = signNames[0]; // 创建签名器,替换已有签名占位 PdfSigner signer = PdfSigner.ReplaceSignature(reader, signName, outStream); var signedContainer = new SignedSignatureContainer(originalDocHash, signedAttrHash, chains, digestAlgorithm); signer.SignExternalContainer(signedContainer, 8192); } } }
3. 修正已签名容器实现
确保参数传递正确,生成合法的PKCS7签名:
public class SignedSignatureContainer : IExternalSignatureContainer { public byte[] OriginalDocHash { get; set; } public byte[] SignedAttrHash { get; set; } public X509Certificate[] CertChains { get; set; } public string DigestAlgorithm { get; set; } public SignedSignatureContainer(byte[] originalDocHash, byte[] signedAttrHash, X509Certificate[] certChains, string digestAlgorithm) { OriginalDocHash = originalDocHash; SignedAttrHash = signedAttrHash; CertChains = certChains; DigestAlgorithm = digestAlgorithm; } public byte[] Sign(Stream data) { PdfPKCS7 sgn = new PdfPKCS7(null, CertChains, DigestAlgorithm, false); // 设置外部签名:传入HSM返回的签名后属性哈希 sgn.SetExternalDigest(SignedAttrHash, null, "RSA"); // 传入第一步获取的原始文档哈希,生成完整PKCS7数据 return sgn.GetEncodedPKCS7(OriginalDocHash, PdfSigner.CryptoStandard.CMS, null, null, null); } public void ModifySigningDictionary(PdfDictionary signDic) { signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite); signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached); } }
4. 修正中间调用逻辑
确保各步骤参数传递正确:
// 第一步:生成临时文件并获取原始文档哈希 var originalDocHash = ComputeHash(inputFile, tempFile, chain, "sign-reason", "", 1, 1); // 生成PKCS7认证属性 PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", false); var authenAttrBytes = sgn.GetAuthenticatedAttributeBytes(originalDocHash, PdfSigner.CryptoStandard.CMS, null, null); // 计算认证属性哈希并发送到HSM签名 var attrHash = SHA256Managed.Create().ComputeHash(authenAttrBytes); byte[] signedAttrHash = SendToHsmForSigning(attrHash); // 替换为你的HSM实际调用代码 // 第二步:替换临时文件中的签名占位 Sign(tempFile, outputFile, chain, originalDocHash, signedAttrHash);
补充说明
EmptySignatureContainer无需修改,当前逻辑已正确计算待签名文档的哈希。- 必须使用
PdfSigner.ReplaceSignature替换签名占位,才能保证PDF结构完整。 - 所有文件流统一使用
FileMode.Create,彻底避免旧数据残留问题。
内容的提问来源于stack exchange,提问作者Phúc Đỗ
相关产品推荐
相关产品推荐

