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

使用iText 7分两步签名PDF后文件损坏,求排查问题

PDF数字签名文件损坏问题排查与修复

核心问题分析

你用iText7分两步签名PDF后文件损坏,主要是以下3个原因:

  1. 签名方式错误:第一步生成的临时文件已包含签名占位,第二步却重新调用SignExternalContainer从头签名,而非替换已有占位,直接破坏了PDF文件结构。
  2. 参数传递错误:SignedSignatureContainer中调用GetEncodedPKCS7时,传入的是认证属性字节,而非第一步计算的原始文档哈希,导致签名结构无效。
  3. 文件流处理不当:第一步用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 Đỗ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 09:57:19