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

C# AES加解密 超16字符文本解密后末尾丢字符问题排查

问题现象

C#项目实现字符串AES加解密功能时,待加密明文长度超过16个字符的场景下,解密结果末尾会丢失部分字符;明文长度较短(如"abcde")时加解密流程正常,无字符缺失。
复现测试代码如下:
Program.cs

string plainText = "abcdefghijklmnopqrstuvwhyz1234567890";
string password = Convert.ToBase64String(Cryptography.GenerateRandomEntropy());
string encryptedText = Cryptography.Encrypt(plainText, password);
string decryptedText = Cryptography.Decrypt(encryptedText, password);

Console.WriteLine("Text: " + plainText); // 预期输出:abcdefghijklmnopqrstuvwhyz1234567890
Console.WriteLine("Pass: " + password);
Console.WriteLine("Enc:  " + encryptedText);
Console.WriteLine("Dec:  " + decryptedText); // 实际输出:abcdefghijklmnopqrstuvwhyz123456

Crypt.cs

using System.Text;
using System.Security.Cryptography;

public static class Cryptography
{
    private const int Keysize = 128;

    private const int DerivationIterations = 1000;

    public static string Encrypt(string plainText, string passPhrase)
    {
        var saltStringBytes = GenerateRandomEntropy();
        var ivStringBytes = GenerateRandomEntropy();
        var plainTextBytes = Encoding.UTF8.GetBytes(plainText);

        using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
        {
            var keyBytes = password.GetBytes(Keysize / 8);
            using (var symmetricKey = Aes.Create())
            {
                symmetricKey.BlockSize = Keysize;
                symmetricKey.Mode = CipherMode.CBC;
                symmetricKey.Padding = PaddingMode.PKCS7;
                using (var key = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        using (var cryptoStream = new CryptoStream(memoryStream, key, CryptoStreamMode.Write))
                        {
                            cryptoStream.Write(plainTextBytes);
                            cryptoStream.FlushFinalBlock();
                            var cryptTextBytes = saltStringBytes;
                            cryptTextBytes = cryptTextBytes.Concat(ivStringBytes).ToArray();
                            cryptTextBytes = cryptTextBytes.Concat(memoryStream.ToArray()).ToArray();
                            memoryStream.Close();
                            cryptoStream.Close();
                            return Convert.ToBase64String(cryptTextBytes);
                        }
                    }
                }
            }
        }
    }

    public static string Decrypt(string cryptText, string passPhrase)
    {
        var cryptTextBytesWithSaltAndIv = Convert.FromBase64String(cryptText);
        var saltStringBytes = cryptTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
        var ivStringBytes = cryptTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
        var cryptTextBytes = cryptTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cryptTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

        using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
        {
            var keyBytes = password.GetBytes(Keysize / 8);
            using (var symmetricKey = Aes.Create())
            {
                symmetricKey.BlockSize = Keysize;
                symmetricKey.Mode = CipherMode.CBC;
                symmetricKey.Padding = PaddingMode.PKCS7;
                using (var key = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                {
                    using (var memoryStream = new MemoryStream(cryptTextBytes))
                    {
                        using (var cryptoStream = new CryptoStream(memoryStream, key, CryptoStreamMode.Read))
                        {
                            var plainTextBytes = new byte[cryptTextBytes.Length];
                            cryptoStream.Read(plainTextBytes);
                            memoryStream.Close();
                            cryptoStream.Close();
                            return Encoding.UTF8.GetString(plainTextBytes);
                        }
                    }
                }
            }
        }
    }

    public static byte[] GenerateRandomEntropy()
    {
        var randomBytes = new byte[Keysize / 8];
        using (var rngCsp = RandomNumberGenerator.Create())
        {
            rngCsp.GetBytes(randomBytes);
        }
        return randomBytes;
    }
}
问题根因

解密逻辑存在两处错误:

  • Stream.Read方法不保证单次调用就能读取完请求的全部字节数。AES-128的块大小为16字节,当密文长度超过单个块大小时,单次调用cryptoStream.Read(plainTextBytes)只会返回第一个块的解密结果,剩余块的内容未被读入缓冲区,直接造成内容截断。
  • 初始化plainTextBytes缓冲区时长度和密文长度一致,但PKCS7填充会让密文比明文长最多16字节,直接用整个缓冲区转字符串会带入末尾无效空字节。
修复方法

替换Decrypt方法中CryptoStream的读取逻辑,通过内置CopyTo方法自动分块读取全部解密后的有效字节,再转字符串即可。修复后的Decrypt方法代码如下:

public static string Decrypt(string cryptText, string passPhrase)
{
    var cryptTextBytesWithSaltAndIv = Convert.FromBase64String(cryptText);
    var saltStringBytes = cryptTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
    var ivStringBytes = cryptTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
    var cryptTextBytes = cryptTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cryptTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

    using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
    {
        var keyBytes = password.GetBytes(Keysize / 8);
        using (var symmetricKey = Aes.Create())
        {
            symmetricKey.BlockSize = Keysize;
            symmetricKey.Mode = CipherMode.CBC;
            symmetricKey.Padding = PaddingMode.PKCS7;
            using (var key = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
            {
                using (var memoryStream = new MemoryStream(cryptTextBytes))
                {
                    using (var cryptoStream = new CryptoStream(memoryStream, key, CryptoStreamMode.Read))
                    {
                        using (var plainMs = new MemoryStream())
                        {
                            cryptoStream.CopyTo(plainMs);
                            var plainTextBytes = plainMs.ToArray();
                            return Encoding.UTF8.GetString(plainTextBytes);
                        }
                    }
                }
            }
        }
    }
}

替换后任意长度的明文都可正常完成加解密,不会出现末尾字符丢失问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 13:45:35