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

C#复用CryptoStream批量加密时解密提示填充无效的问题排查

问题

我尝试对大量独立值进行加密,每个值加密后需能解密还原为原始值。为避免重复创建CryptoStream,我选择复用现有流,但在解密单个值时遇到了无法解决的问题:抛出CryptographicException异常,提示“Padding is invalid and cannot be removed”。

我已验证每个读取的数据块都能被8字节整除,以适配所使用的AES加密器的128位块大小。请问我哪里出错了?

加密代码:

var results = new Dictionary<string, string>();
var aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
var encryptor = aes.CreateEncryptor(Key, iv);
using (var outputStream = new MemoryStream())
{
    using (var cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
    {
        long writeStart = 0;
        foreach (var toEncrypt in allToEncrypt)
        {
            var bytesToEncrypt= Encoding.ASCII.GetBytes(toEncrypt);
            cryptoStream.Write(bytesToEncrypt, 0, bytesToEncrypt.Length);
            cryptoStream.Flush();

            long count =  outputStream.Position - writeStart;

            outputStream.Seek(writeStart, SeekOrigin.Begin);
            var encryptedBytes = new byte[(int)count];
            outputStream.Read(encryptedBytes, 0, encryptedBytes.Length);
            results.Add(toEncrypt, formattedIV + EncryptionFormatter(encryptedBytes));

            writeStart = outputStream.Position;
        }
    }
}

编辑说明:
关于仅贴出加密代码但问题出在解密的疑问:实际是加密逻辑存在错误,导致解密时抛出异常。单值加密的正常逻辑如下(解密流程也基于此简单模式):

单值加密代码:

using (var outputStream = new MemoryStream())
{
    using (var cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
    {
        var bytes = Encoding.ASCII.GetBytes(toEncrypt);
        cryptoStream.Write(bytes, 0, bytes.Length);
        cryptoStream.FlushFinalBlock();
    }

    return EncryptionFormatter(iv, outputStream.ToArray());
}

单值解密代码:

using (var outputStream = new MemoryStream())
{
    using (var cryptoStream = new CryptoStream(outputStream, decryptor, CryptoStreamMode.Write))
    {
        cryptoStream.Write(bytes, 0, bytes.Length);
        cryptoStream.FlushFinalBlock();
        return DecryptionFormatter(outputStream.ToArray());
    }
}
解决方案

核心问题分析

  1. CBC模式的链式依赖破坏独立性
    CBC模式下,每个加密块的输出会自动作为下一个加密块的IV。你复用同一个CryptoStream加密多个独立值,会导致后一个值的加密依赖前一个值的加密结果,而非初始IV。解密单个值时你用的是初始IV,但实际加密该值时的IV已经是前一个加密块的输出,直接导致解密逻辑不匹配。

  2. 未正确处理PKCS7填充
    单值加密时调用FlushFinalBlock()会自动添加PKCS7填充并完成最后一个块的加密,而你复用流时只调用了Flush()——这个方法仅刷新缓冲区,不会处理填充或完成当前块加密。你读取的加密字节只是不完整的中间数据,缺少必要的填充信息,解密时自然无法验证填充有效性。

  3. 流缓冲导致的数据不完整
    CryptoStream是缓冲式设计,调用Flush()后并非所有加密数据都会立即写入底层MemoryStream。你直接读取MemoryStream内容,大概率拿到的是不完整的加密片段。

修正方案

  • 放弃复用CryptoStream:CBC模式下每个独立值的加密都需要独立的加密上下文(包括独立IV),复用流会彻底破坏加密的独立性。创建CryptoStream的开销极小,不会成为性能瓶颈。
  • 为每个值生成独立IV:IV不需要保密,推荐为每个加密值生成新IV,并和密文一起存储,解密时用对应IV即可。

正确的批量加密示例:

var results = new Dictionary<string, string>();
var aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;

foreach (var toEncrypt in allToEncrypt)
{
    // 为每个值生成独立IV
    aes.GenerateIV();
    var iv = aes.IV;
    var encryptor = aes.CreateEncryptor(Key, iv);
    
    using (var outputStream = new MemoryStream())
    {
        using (var cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
        {
            var bytesToEncrypt = Encoding.ASCII.GetBytes(toEncrypt);
            cryptoStream.Write(bytesToEncrypt, 0, bytesToEncrypt.Length);
            cryptoStream.FlushFinalBlock();
        }
        var encryptedBytes = outputStream.ToArray();
        results.Add(toEncrypt, EncryptionFormatter(iv) + EncryptionFormatter(encryptedBytes));
    }
}

解密时只需从密文中提取对应IV,再用该IV创建解密器即可正常解密。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:40:18