C# 验证PDF签名 C#如何检查PDF文件中的数字签名是否有效

1次阅读

pdf签名验证失败但无报错,主因是仅存在空签名域而非有效PKCS#7签名;证书链不可信常因吊销检查网络失败;iTextSharp解析易截断Contents字节;可信时间须依赖DSS中TSA而非M字段。

C# 验证PDF签名 C#如何检查PDF文件中的数字签名是否有效

PDF签名验证失败但没报错?先确认是否在验证签名本身

很多开发者调用 SignatureCollectionAcroForm.Signatures 后发现 IsValid 返回 false,却没意识到:PDF里可能根本没嵌入可验证的数字签名——它只是带了个签名域(SignatureField),而域里空着或只存了占位图像。真正的签名数据必须是符合 PKCS#7(或 CMS)格式的二进制对象,并绑定到某个字段的 Value 中。

实操建议:

  • pdfiumsharpiTextSharp.LGPLv2.Core 打开文件后,先遍历所有 SignatureField,检查其 FieldValue 是否非空且类型为 ByteString
  • 若用 System.Security.Cryptography.Pkcs.SignedCms 解析,需先提取原始字节流,再调用 Decode();直接传入 PDF 字段的 Base64 编码字符串会抛 CryptographicException
  • 注意 Adobe Reader 允许“视觉签名”(仅图片+文字),这类签名 SignatureCollection 根本不会收录——它只认 ISO 32000-1 定义的 Sig 字典项

证书链不可信?别硬写 Verify() 就完事

调用 signature.Verify() 返回 false,常见原因是系统信任根证书库(windows Certificate Store)里缺中间 CA 或 OCSP 响应不可达,而不是签名本身损坏。.NET 默认启用吊销检查(X509RevocationMode.Online),一旦网络不通或 CA 服务器返回 5xx,验证就静默失败。

实操建议:

  • 改用 Verify(X509RevocationMode.NoCheck) 快速排除网络干扰;但上线前必须切回 OnlineOffline 并配好 X509RevocationFlag.ExcludeRoot
  • 手动加载证书链时,确保按“叶子→中间→根”顺序添加到 X509Chain.ChainPolicy.ExtraStore,顺序反了会导致 PartialChain 错误
  • 如果证书含 AIA(Authority Information access)扩展,.NET 会自动尝试下载 CRL/OCSP;某些企业内网屏蔽外部 https,得提前缓存 CRL 到本地并用 ChainPolicy.CustomTrustStore 注入

用 iTextSharp 验证时提示 BadPaddingException?大概率是签名字节被截断

iTextSharp.text.pdf.AcroFields.GetSignatureNames() 能列出签名名,但 GetSignatureDictionary() 返回的 PdfDictionary 里,Contents 的值可能是不完整的十六进制字符串(如 形式),直接转 byte[] 会丢字节。iText 对 PDF 签名解析依赖底层 PdfReader 的流解码逻辑,跳过对象流或交叉引用表偏移错误都会导致内容截断。

实操建议:

  • 不用 PdfString.ToUnicodeString() 处理 Contents,改用 PdfString.GetOriginalBytes() 获取原始字节
  • 验证前先调用 reader.ConsolidateNamedDestinations()reader.RemoveUnusedObjects(),避免因 PDF 结构损坏导致签名字节读取错位
  • 若用 iText7.kernel.pdf.PdfDocument,必须打开时传 new PdfReader(src, new ReaderProperties().SetUnethicalReading(true)),否则加密/增量更新的 PDF 可能拒绝访问签名数据

时间戳失效或签名时间早于证书有效期?检查 PDF 文档时间戳(DSS)和签署时间字段

PDF 签名有效 ≠ 文档可信。签名时间由 M 字段(如 D:20230101120000+08'00')声明,但它可被任意设置;而真实可信的时间依据是签名中嵌入的 RFC 3161 时间戳令牌(TSA)。若 PDF 没包含 DSS(Document Security Store)或 TSA 证书不在信任链里,即使签名验签通过,法律效力也存疑。

实操建议:

  • pdfiumsharpGetSignatureTimestamp() 提取 TSA 时间,对比系统时间与证书有效期,而非只信 M 字段
  • 检查 PDF 是否含 DSS 字典(路径:Root/DSS),里面应有 CertificatesRevocationsTimestamps 子项;缺失则说明未启用长期验证(LTV)
  • 生成签名时若用 iText7,务必调用 signer.SetCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING_AND_ANNOTATION) 并启用 EnableLtvForCollection(),否则 DSS 不会自动注入

真正麻烦的是混合签名场景:一个 PDF 里多个签名、部分带 TSA、部分不带,还跨不同时间戳服务。这时候不能只看单个 IsValid,得逐签名解析 SignerInfo,比对 SigningTime 属性和各自嵌入的 TSA 证书链——这些细节,调试器里点开 signature.CryptoValue 才看得见。

text=ZqhQzanResources