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

数字签名SignDetached时出现cannot access closed stream错误求助

解决SignDetached操作中的"cannot access closed stream"错误

我仔细看了你的代码,这个流关闭异常主要是因为资源管理混乱、操作顺序错误以及冗余代码导致的,下面我来逐一分析问题并给出修正方案:

核心问题分析

  1. 手动关闭资源干扰using块的自动释放:你在using块里手动调用reader.Close()、ms.Close(),但using语句本来就会在代码块结束时自动释放资源,手动关闭会导致流提前被关闭,后续操作(比如ms.ToArray())就会报错。
  2. 流复用与操作顺序错误:你在加水印的PdfStamper还没完成写入时,就用同一个ms创建签名的PdfStamper,同时签名时用的是原始字节数组的PdfReader,完全没用到加水印后的内容,这不仅逻辑错误,还会导致流状态冲突。
  3. 冗余的代码位置:密钥对、证书生成和签名逻辑完全不需要放在页面循环里,你循环一次就break,既浪费资源又打乱了流的正常操作流程。
  4. 未完成加水印就进入签名:加水印的stamper还没完成所有页面的水印写入,就被签名代码打断,导致流内的数据不完整,后续操作自然会出问题。

修正后的完整代码

public static byte[] AddWatermarkAndSign(byte[] bytes, BaseFont baseFont, string watermarkText)
{
    // 第一步:先给所有页面添加水印,得到加水印后的PDF字节数组
    byte[] watermarkedBytes;
    using (var msWatermark = new MemoryStream())
    {
        using (var reader = new PdfReader(bytes))
        using (var stamper = new PdfStamper(reader, msWatermark))
        {
            int pageCount = reader.NumberOfPages;
            for (int i = 1; i <= pageCount; i++)
            {
                var content = stamper.GetOverContent(i);
                AddWaterMarkText(content, watermarkText, baseFont, 100, 45, BaseColor.GRAY, reader.GetPageSizeWithRotation(i));
            }
            // using块会自动关闭stamper和reader,确保水印完全写入msWatermark
        }
        watermarkedBytes = msWatermark.ToArray();
    }

    // 第二步:生成密钥对与自签名证书(这部分可以单独抽成方法复用)
    var keyGenerator = new RsaKeyPairGenerator();
    keyGenerator.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
    var keyPair = keyGenerator.GenerateKeyPair();

    var certGenerator = new X509V3CertificateGenerator();
    certGenerator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
    certGenerator.SetSubjectDN(new X509Name("CN=abc.com"));
    certGenerator.SetIssuerDN(new X509Name("CN=Aalok"));
    certGenerator.SetNotBefore(DateTime.Now);
    certGenerator.SetNotAfter(DateTime.Now.AddYears(1));
    certGenerator.SetSignatureAlgorithm(HashType.SHA256withRSA.ToString());
    certGenerator.SetPublicKey(keyPair.Public);
    Org.BouncyCastle.X509.X509Certificate cert = certGenerator.Generate(keyPair.Private);

    // 可选:保存证书和PFX到本地(不需要的话可以删除这部分)
    try
    {
        using (var derStream = new FileStream("abc.der", FileMode.Create, FileAccess.ReadWrite))
        {
            byte[] certBytes = cert.GetEncoded();
            derStream.Write(certBytes, 0, certBytes.Length);
        }
    }
    catch (FileNotFoundException)
    {
        Console.WriteLine("无法找到文件路径");
    }

    var pkcsStore = new Pkcs12StoreBuilder().Build();
    var certEntry = new X509CertificateEntry(cert);
    pkcsStore.SetCertificateEntry(cert.SubjectDN.ToString(), certEntry);
    var keyEntry = new AsymmetricKeyEntry(keyPair.Private);
    pkcsStore.SetKeyEntry($"{cert.SubjectDN.ToString()}_key", keyEntry, new[] { certEntry });

    try
    {
        using (var pfxStream = new FileStream(@"abc.pfx", FileMode.Create, FileAccess.ReadWrite))
        {
            pkcsStore.Save(pfxStream, "123".ToCharArray(), new SecureRandom());
        }
    }
    catch (FileNotFoundException)
    {
        Console.WriteLine("无法找到文件路径");
    }

    // 第三步:对加水印后的PDF进行数字签名
    using (var msSignature = new MemoryStream())
    {
        using (var reader = new PdfReader(watermarkedBytes))
        using (var stamper = PdfStamper.CreateSignature(reader, msSignature, '\0'))
        {
            var signatureAppearance = stamper.SignatureAppearance;
            signatureAppearance.Reason = "签名原因说明";
            signatureAppearance.Location = "签名地点";
            signatureAppearance.SetVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");

            // 获取私钥和证书链
            string alias = string.Empty;
            foreach (string al in pkcsStore.Aliases)
            {
                if (pkcsStore.IsKeyEntry(al) && pkcsStore.GetKey(al).Key.IsPrivate)
                {
                    alias = al;
                    break;
                }
            }

            var privateKeyEntry = pkcsStore.GetKey(alias);
            var certChain = new List<Org.BouncyCastle.X509.X509Certificate>();
            foreach (X509CertificateEntry entry in pkcsStore.GetCertificateChain(alias))
            {
                certChain.Add(entry.Certificate);
            }

            var rsaParams = privateKeyEntry.Key as RsaPrivateCrtKeyParameters;
            var externalSignature = new PrivateKeySignature(rsaParams, DigestAlgorithms.SHA256);
            MakeSignature.SignDetached(signatureAppearance, externalSignature, certChain, null, null, null, 0, CryptoStandard.CMS);
        }
        return msSignature.ToArray();
    }
}

关键修正细节

  • 拆分独立流程:把加水印、证书生成、数字签名拆成三个独立步骤,每个步骤用单独的MemoryStream,彻底避免流冲突。
  • 依赖using自动管理资源:所有实现IDisposable的对象都放在using块中,不再手动调用Close(),确保资源在合适的时机释放。
  • 基于加水印后的内容签名:签名时使用加水印完成后的watermarkedBytes创建PdfReader,保证签名的是最终的PDF内容。
  • 移除冗余循环:密钥对和证书生成逻辑移到页面循环外,避免重复创建资源,同时确保所有页面都添加了水印。
  • 确保流写入完成:在using块结束后再获取流的字节数组,保证所有写入操作都已完成,不会读取到不完整的数据。

内容的提问来源于stack exchange,提问作者user14418741

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 14:17:27