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
相关产品推荐
相关产品推荐

