使用AES256加解密后明文不匹配,请求排查问题(附代码)
问题分析与修复方案
你遇到的解密后字符串不匹配问题,核心原因是加密后的字节数组被错误地用ASCII编码转换为字符串,导致数据丢失;另外还有几个细节需要调整,下面一步步帮你解决:
1. 核心错误:ASCII编码无法安全处理加密字节
加密后的字节数组包含大量非ASCII可打印字符,直接用Encoding.ASCII.GetString(encrypted)转换时,无法识别的字节会被强制替换为?(ASCII值63)。后续再用Encoding.ASCII.GetBytes(cipher)转回字节数组时,已经不是原始的加密数据了,自然解密不出正确结果。
正确的做法是用Base64编码处理二进制数据,它专门用来将字节安全转换为可打印字符串,不会丢失信息。
2. 代码修复步骤
修复Program.cs的加密解密流程
替换ASCII编码为Base64,同时统一使用UTF8编码处理字符串(支持更多字符,比ASCII更实用):
using System; using System.Text; namespace AESEncryptionUtility { class Program { private static string _pass = "MasterPass"; private static string _msg = "Mohit"; private static byte[] key = EncryptionUtility.GenerateKey(_pass, 32); private static byte[] IV = EncryptionUtility.GenerateSalt(16); static void Main(string[] args) { Console.WriteLine("Hello World!"); string encryptedBase64 = Encrypt(_msg); string decryptedString = Decrypt(encryptedBase64); Console.WriteLine($"原字符串:{_msg}"); Console.WriteLine($"解密后字符串:{decryptedString}"); } public static string Encrypt(string msg) { byte[] plainBytes = Encoding.UTF8.GetBytes(msg); byte[] encrypted = EncryptionUtility.Encrypt(plainBytes, key, IV); Console.WriteLine("加密后的字节:"); foreach (var b in encrypted) { Console.Write(b + " "); } Console.WriteLine("\n"); // 用Base64转换为可存储/传输的字符串 return Convert.ToBase64String(encrypted); } public static string Decrypt(string cipherBase64) { // 从Base64转回原始加密字节 byte[] cipherBytes = Convert.FromBase64String(cipherBase64); byte[] decryptedBytes = EncryptionUtility.Decrypt(cipherBytes, key, IV); Console.WriteLine("解密后的字节:"); foreach (var b in decryptedBytes) { Console.Write(b + " "); } Console.WriteLine("\n"); // 用UTF8转回字符串,和加密时编码一致 return Encoding.UTF8.GetString(decryptedBytes); } } }
修复EncryptionUtility.cs的细节
- 把
PaddingMode.Zeros改回PaddingMode.PKCS7:Zero padding无法区分原始数据末尾的空字符和填充的空字符,PKCS7是更安全、更通用的填充方式; - 优化
GenerateKey方法:如果需要持久化密钥(比如下次解密复用),要把生成的salt保存下来,否则每次调用都会生成不同的密钥。
修改后的EncryptionUtility.cs:
using System; using System.Security.Cryptography; namespace AESEncryptionUtility { public static class EncryptionUtility { public static byte[] Encrypt(byte[] plainBytes, byte[] key, byte[] IV) { using (AesCryptoServiceProvider aesAlgo = new AesCryptoServiceProvider()) { aesAlgo.Key = key; aesAlgo.BlockSize = 128; aesAlgo.Mode = CipherMode.CBC; aesAlgo.Padding = PaddingMode.PKCS7; // 改用PKCS7填充 aesAlgo.IV = IV; ICryptoTransform encryptor = aesAlgo.CreateEncryptor(); return encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length); } } public static byte[] Decrypt(byte[] cipherBytes, byte[] key, byte[] IV) { using (AesCryptoServiceProvider aesAlgo = new AesCryptoServiceProvider()) { aesAlgo.Key = key; aesAlgo.BlockSize = 128; aesAlgo.Mode = CipherMode.CBC; aesAlgo.Padding = PaddingMode.PKCS7; // 和加密端保持一致 aesAlgo.IV = IV; ICryptoTransform decryptor = aesAlgo.CreateDecryptor(); return decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); } } // 新增带salt输出的方法,方便持久化密钥 public static byte[] GenerateKey(string masterPassword, int size, out byte[] salt) { salt = GenerateSalt(size); Rfc2898DeriveBytes pbfdk = new Rfc2898DeriveBytes(masterPassword, salt, 20000); return pbfdk.GetBytes(size); } // 保留原有方法兼容现有代码 public static byte[] GenerateKey(string masterPassword, int size) { return GenerateKey(masterPassword, size, out _); } public static byte[] GenerateSalt(int size) { using (RNGCryptoServiceProvider generator = new RNGCryptoServiceProvider()) { byte[] salt = new byte[size]; generator.GetNonZeroBytes(salt); return salt; } } } }
3. 额外注意事项
- 加密和解密必须使用相同的编码(这里统一用UTF8),避免出现乱码;
- 实际应用中,IV和salt都需要和加密内容一起保存(比如存数据库或文件):IV应该每次加密都生成新的,而密钥生成依赖salt;
- .NET Core/.NET 5+中推荐用
RandomNumberGenerator替代RNGCryptoServiceProvider,安全性更高; - 你的代码已经正确使用了32字节密钥对应AES-256,这部分没问题。
修改完成后,解密后的字符串就会和原字符串完全匹配了。
内容的提问来源于stack exchange,提问作者kumarmo2
相关产品推荐
相关产品推荐

