You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用iTextSharp签署PDF遇无效签名:文档篡改报错求助

问题排查与修复方案

你的代码存在多处逻辑错误,导致签名后PDF提示篡改,核心问题在于签名流程混乱和远程签名实现错误,具体问题及修复如下:

核心问题分析

  1. 混合两种签名流程:你在GetBytesToSign中同时调用了MakeSignature.SignDetached(完整签名流程)和手动构建PKCS7签名,这会导致临时PDF已被错误签名,后续的SignDeferred操作完全无效。
  2. RemoteSignature类实现错误:Sign方法直接返回服务端的签名结果signBytes,但iTextSharp调用该方法时传入的是需要签名的原始数据(即PDF待签内容的哈希相关字节),你应该让服务端对这个传入的原始数据进行签名,而不是直接返回预先生成的签名。
  3. 错误构建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 14:54:55