使用iTextSharp签署PDF遇无效签名:文档篡改报错求助
问题排查与修复方案
你的代码存在多处逻辑错误,导致签名后PDF提示篡改,核心问题在于签名流程混乱和远程签名实现错误,具体问题及修复如下:
核心问题分析
- 混合两种签名流程:你在
GetBytesToSign中同时调用了MakeSignature.SignDetached(完整签名流程)和手动构建PKCS7签名,这会导致临时PDF已被错误签名,后续的SignDeferred操作完全无效。 - RemoteSignature类实现错误:
Sign方法直接返回服务端的签名结果signBytes,但iTextSharp调用该方法时传入的是需要签名的原始数据(即PDF待签内容的哈希相关字节),你应该让服务端对这个传入的原始数据进行签名,而不是直接返回预先生成的签名。 - 错误构建PKCS7签名:你手动计算哈希并构建PKCS7的逻辑完全错误,正确的远程签名应该依赖iTextSharp的API来生成待签数据,再传入服务端签名结果。
修复后的代码实现
修正Signpdf类
class Signpdf { // 第一步:生成待签名的临时PDF,并获取需要发送给服务端签名的字节数组 public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, List<Org.BouncyCastle.X509.X509Certificate> chain) { if (File.Exists(tempPdf)) File.Delete(tempPdf); using (PdfReader reader = new PdfReader(unsignedPdf)) { using (FileStream os = new FileStream(tempPdf, FileMode.Create, FileAccess.Write, FileShare.None)) { PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0'); PdfSignatureAppearance appearance = stamper.SignatureAppearance; // 设置签名外观参数 appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName); appearance.Certificate = chain[0]; appearance.SignDate = DateTime.Now; appearance.Reason = "test"; appearance.Location = "test"; // 创建临时签名容器,只预留签名位置,不完成签名 ExternalBlankSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED); MakeSignature.SignExternalContainer(appearance, external, 8192); // 获取需要签名的字节流(PDF中待验证的内容) byte[] contentHash = DigestAlgorithms.Digest(appearance.GetRangeStream(), "SHA256"); // 生成PKCS7的认证属性字节(服务端需要对这个字节数组的SHA256哈希进行签名) PdfPKCS7 pkcs7 = new PdfPKCS7(null, chain, "SHA256", false); byte[] authenticatedAttributes = pkcs7.getAuthenticatedAttributeBytes(contentHash, null, null, CryptoStandard.CMS); return authenticatedAttributes; } } } // 第二步:将服务端返回的签名嵌入临时PDF public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, byte[] serviceHashSign, List<Org.BouncyCastle.X509.X509Certificate> chain) { using (PdfReader reader = new PdfReader(tempPdf)) { using (FileStream os = new FileStream(signedPdf, FileMode.Create, FileAccess.Write, FileShare.None)) { // 构建签名容器,传入服务端的签名结果 IExternalSignatureContainer external = new MyExternalSignatureContainer(serviceHashSign, chain); MakeSignature.SignDeferred(reader, signatureFieldName, os, external); } } } private class MyExternalSignatureContainer : IExternalSignatureContainer { private readonly byte[] _signedBytes; private readonly List<Org.BouncyCastle.X509.X509Certificate> _chain; public MyExternalSignatureContainer(byte[] signedBytes, List<Org.BouncyCastle.X509.X509Certificate> chain) { _signedBytes = signedBytes; _chain = chain; } public byte[] Sign(Stream data) { // 基于服务端签名生成完整的PKCS7签名 PdfPKCS7 pkcs7 = new PdfPKCS7(null, _chain, "SHA256", false); pkcs7.SetExternalDigest(_signedBytes, null, "RSA"); // 获取PDF中待签内容的哈希 byte[] contentHash = DigestAlgorithms.Digest(data, "SHA256"); return pkcs7.GetEncodedPKCS7(contentHash, null, null, null, CryptoStandard.CMS); } public void ModifySigningDictionary(PdfDictionary signDic) { // 设置签名算法 signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKLITE); signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED); } } }
修正RemoteSignature类(可选,修复流程中可废弃)
// 若采用上述修复流程,此类可废弃;如需保留,需改为传入签名委托 class RemoteSignature : IExternalSignature { private readonly Func<byte[], byte[]> _signFunc; // 传入签名委托,接收待签数据,返回服务端签名结果 public RemoteSignature(Func<byte[], byte[]> signFunc) { _signFunc = signFunc; } public virtual byte[] Sign(byte[] message) { // 调用服务端签名接口,对传入的message进行签名 return _signFunc(message); } public virtual String GetHashAlgorithm() { return "SHA-256"; } public virtual String GetEncryptionAlgorithm() { return "RSA"; } }
正确的签名流程调用示例
// 1. 准备证书链、待签名PDF路径 List<Org.BouncyCastle.X509.X509Certificate> chain = ...; // 你的证书链 string unsignedPdf = "unsigned.pdf"; string tempPdf = "temp.pdf"; string signedPdf = "signed.pdf"; string signatureFieldName = "MySignature"; // 2. 获取需要发送给服务端签名的字节数组 byte[] dataToSign = Signpdf.GetBytesToSign(unsignedPdf, tempPdf, signatureFieldName, chain); // 3. 调用服务端签名接口:对dataToSign计算SHA256哈希,用RSA私钥签名后返回字节数组 byte[] serviceHashSign = ...; // 从服务端获取的签名字节数组(Base64解码后) // 4. 将签名嵌入临时PDF,生成最终已签名PDF Signpdf.EmbedSignature(tempPdf, signedPdf, signatureFieldName, serviceHashSign, chain);
关键注意事项
- 服务端签名逻辑必须是:对传入的
dataToSign计算SHA256哈希,然后用RSA私钥对该哈希进行签名,返回的字节数组即为serviceHashSign。 - 确保证书链完整有效,包含所有中间证书和根证书。
- 签名全程使用的哈希算法必须一致(示例中为SHA256)。
内容的提问来源于stack exchange,提问作者Hoàng Trung Nam
相关产品推荐
相关产品推荐

