.NET Framework中AES解密失败问题求助
AES加密用户名后解密结果为空的排查与解决
问题描述
尝试通过HTTP头向服务端发送AES加密的用户名(已知有更优实现方式),但解密后的字符串始终为空。
加密代码(客户端.NET 8)
using var aes = Aes.Create(); aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.ECB; aes.GenerateIV(); aes.Key = SHA256.HashData(Encoding.Unicode.GetBytes(PK)); var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using MemoryStream memoryStream = new MemoryStream(); using CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write); using StreamWriter streamWriter = new StreamWriter(cryptoStream, Encoding.Unicode); string payload = UserProvider.User; streamWriter.Write(payload); cryptoStream.FlushFinalBlock(); byte[] array = memoryStream.ToArray(); string encryptedString = Convert.ToBase64String(array); string ivString = Convert.ToBase64String(aes.IV); _client.AddDefaultHeader("user", encryptedString); _client.AddDefaultHeader("iv", ivString);
解密代码(服务端.NET Framework 4.8)
internal static class Authentication { private const string PK = "MySecretKey"; public static string DecryptUser(string encrypted, string iv) { using (var aes = Aes.Create()) using (var sha = SHA256.Create()) { byte[] pkBytes = Encoding.Unicode.GetBytes(PK); byte[] pkHash = sha.ComputeHash(pkBytes); byte[] ivBytes = Convert.FromBase64String(iv); aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.ECB; aes.Key = pkHash; aes.IV = ivBytes; var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); byte[] cipher = Convert.FromBase64String(encrypted); using (MemoryStream memoryStream = new MemoryStream(cipher)) using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) using (StreamReader streamReader = new StreamReader(cryptoStream)) { string user = streamReader.ReadToEnd(); if (user.IsNullOrWhiteSpace()) throw new Exception("Authentication failed."); return user; } } } }
已排查项
- CipherMode配置正确
- PaddingMode配置正确
- 接收的Base64字符串非空且未损坏
- 尝试过将MemoryStream定位到起始位置
- 尝试发送更长内容,排除解密器最小块大小问题
- 尝试将CryptoStream复制到另一个MemoryStream,但结果缓冲区为空
- 执行ReadToEnd()后,cryptoStream.HasFlushedFinalBlock从false变为true
环境说明
- 服务端(解密侧):.NET Framework 4.8
- 客户端(加密侧):.NET 8
补充测试代码(xUnit,排除Base64/HTTP环节)
public class UnitTest1 { [Fact] public void TestMethod1() { byte[] buffer = new byte[1024]; using (var aes = Aes.Create()) using (var sha = SHA256.Create()) { aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.ECB; aes.Key = sha.ComputeHash(Encoding.Unicode.GetBytes("Password")); using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV)) using (MemoryStream memoryStream = new MemoryStream()) using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) using (StreamWriter streamWriter = new StreamWriter(cryptoStream, Encoding.Unicode)) { string payload = "username"; streamWriter.Write(payload); cryptoStream.FlushFinalBlock(); buffer = memoryStream.ToArray(); Assert.NotEmpty(buffer); } using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) using (MemoryStream memoryStream = new MemoryStream(buffer)) using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) using (StreamReader streamReader = new StreamReader(cryptoStream, Encoding.Unicode)) { string user = streamReader.ReadToEnd(); Assert.True(streamReader.EndOfStream); Assert.True(cryptoStream.HasFlushedFinalBlock); Assert.Equal("username", user); } } } }
问题根源与解决
核心问题1:ECB模式错误使用IV
AES的ECB模式为无IV模式,对每个块独立加密,不需要初始化向量。但当前代码中:
- 加密侧生成并传递IV给加密器,还通过HTTP头发送IV
- 解密侧接收并设置IV到Aes实例
虽然ECB模式会忽略IV参数,但这种冗余操作可能引发状态不一致,同时增加不必要的复杂度。
核心问题2:加密与解密的编码不匹配
加密侧使用Encoding.Unicode(UTF-16LE)写入数据,但服务端解密侧的StreamReader未指定编码,默认使用UTF-8读取,导致解密后的UTF-16字节被错误解析,最终得到空字符串。
核心问题3:解密时数据读取不彻底
直接用StreamReader读取CryptoStream可能因内部缓冲机制,无法触发填充字节的自动移除,导致数据读取不完整。
修复步骤
1. 移除ECB模式下的IV相关代码
- 加密侧:删除
aes.GenerateIV(),将aes.CreateEncryptor(aes.Key, aes.IV)改为aes.CreateEncryptor(),不再发送iv请求头 - 解密侧:删除IV参数接收与设置逻辑,将
aes.CreateDecryptor(aes.Key, aes.IV)改为aes.CreateDecryptor()
2. 统一加密解密的编码
解密侧的StreamReader必须指定Encoding.Unicode,与加密侧保持一致。
3. 优化解密数据读取方式
先将CryptoStream的内容复制到MemoryStream,再用StreamReader读取,确保所有解密数据被处理,同时触发填充字节的自动移除。
修复后的测试代码示例
public class UnitTest1 { [Fact] public void TestMethod1() { byte[] buffer = new byte[1024]; using (var aes = Aes.Create()) using (var sha = SHA256.Create()) { aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.ECB; aes.Key = sha.ComputeHash(Encoding.Unicode.GetBytes("Password")); // 加密部分 using (var encryptor = aes.CreateEncryptor()) using (MemoryStream memoryStream = new MemoryStream()) using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) using (StreamWriter streamWriter = new StreamWriter(cryptoStream, Encoding.Unicode)) { string payload = "username"; streamWriter.Write(payload); cryptoStream.FlushFinalBlock(); buffer = memoryStream.ToArray(); Assert.NotEmpty(buffer); } // 解密部分 using (var decryptor = aes.CreateDecryptor()) using (MemoryStream memoryStream = new MemoryStream(buffer)) using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) using (MemoryStream decryptedStream = new MemoryStream()) { cryptoStream.CopyTo(decryptedStream); decryptedStream.Position = 0; using (StreamReader streamReader = new StreamReader(decryptedStream, Encoding.Unicode)) { string user = streamReader.ReadToEnd(); Assert.Equal("username", user); } } } } }
修复后的服务端解密代码示例
internal static class Authentication { private const string PK = "MySecretKey"; public static string DecryptUser(string encrypted) { using (var aes = Aes.Create()) using (var sha = SHA256.Create()) { byte[] pkBytes = Encoding.Unicode.GetBytes(PK); byte[] pkHash = sha.ComputeHash(pkBytes); aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.ECB; aes.Key = pkHash; var decryptor = aes.CreateDecryptor(); byte[] cipher = Convert.FromBase64String(encrypted); using (MemoryStream memoryStream = new MemoryStream(cipher)) using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) using (MemoryStream decryptedStream = new MemoryStream()) { cryptoStream.CopyTo(decryptedStream); decryptedStream.Position = 0; using (StreamReader streamReader = new StreamReader(decryptedStream, Encoding.Unicode)) { string user = streamReader.ReadToEnd(); if (string.IsNullOrWhiteSpace(user)) throw new Exception("Authentication failed."); return user; } } } } }
内容的提问来源于stack exchange,提问作者Mateus Zampol
相关产品推荐
相关产品推荐

