.NET AES加密解密实战:从原理到生产级实现指南
1. 项目概述与核心价值在.NET开发中数据安全是一个绕不开的话题。无论是保护用户密码、加密配置文件还是确保网络传输中的敏感信息不被窃取我们都需要一套可靠、易用的加密方案。AES高级加密标准作为目前全球公认最安全、最高效的对称加密算法之一无疑是我们的首选。然而很多开发者尤其是刚接触加密领域的同行往往对AES在.NET中的使用感到困惑Aes、AesCryptoServiceProvider、AesManaged这些类有什么区别CBC、ECB这些模式又该如何选择密钥和IV初始化向量到底该怎么管理今天我们就来彻底拆解一个名为“AESEncrypt”的DEMO项目。这不仅仅是一个简单的加密解密示例而是一个旨在让你深入理解.NET框架中AES加密解密机制、核心参数配置以及最佳实践的综合指南。我会结合自己多年在金融和物联网领域处理敏感数据的实战经验带你从“会用”到“懂原理”再到“能避坑”。无论你是需要为下一个项目快速集成加密功能还是想系统性地掌握.NET中的加密体系这篇文章都将为你提供一份详尽的“操作手册”和“原理图”。2. AES加密基础与.NET实现选型在动手写代码之前我们必须先搞清楚几个核心概念。AES是一种分组对称加密算法。所谓“对称”意味着加密和解密使用同一把密钥。“分组”则是指它会把待加密的明文数据切成固定大小的块AES固定为128位即16字节进行处理。2.1 关键参数解析密钥、IV、模式和填充一个完整的AES加密过程除了算法本身还依赖于几个关键参数它们共同决定了加密的强度和行为密钥 (Key)这是加密的核心秘密。AES支持128位、192位和256位三种长度的密钥。密钥越长安全性越高但计算开销也略大。在.NET中Aes类会根据你设置的KeySize属性自动生成或验证密钥长度。初始化向量 (IV)这是一个随机数用于确保即使相同的明文、相同的密钥加密后也会产生不同的密文。这对于防止攻击者通过模式分析破解数据至关重要。IV不需要保密但必须唯一且不可预测。通常每次加密都生成一个新的随机IV并随密文一起存储或传输。加密模式 (Cipher Mode)定义了如何将多个数据块链接起来加密。常见的有ECB (Electronic Codebook)绝对不要在生产环境使用每个数据块独立加密相同的明文块会产生相同的密文块会泄露数据模式。CBC (Cipher Block Chaining)最常用、推荐默认使用的模式。每个明文块在加密前会先与前一个密文块进行异或操作。第一个块则与IV进行异或。这确保了密文的雪崩效应。CFB/OFB将分组密码转换为流密码的模式适用于需要逐字节加密的场景但在.NET中不如CBC常用。填充模式 (Padding Mode)由于AES按块16字节处理当明文长度不是16字节的整数倍时就需要填充。.NET提供了几种方式PKCS7 (默认)填充每个字节的值等于需要填充的字节数。这是最通用和推荐的方式。Zeros用零填充但若明文本身末尾就有零解密时可能无法正确去除。None不填充要求明文长度必须是块大小的整数倍。2.2 .NET中的Aes类族如何选择在System.Security.Cryptography命名空间下你会遇到几个以Aes开头的类它们的区别是很多人的困惑点Aes(抽象基类)这是我们的主要入口。通过静态方法Aes.Create()来创建实例。在.NET Core/.NET 5及更新的.NET Framework版本中这个方法默认返回当前平台最优的实现在Windows上通常是AesCryptoServiceProvider它调用操作系统的CryptoAPI性能好且经过FIPS认证。AesCryptoServiceProvider在Windows上这是基于CryptoAPI的封装实现。如果你明确需要FIPS 140-2合规性或者目标环境是旧版.NET Framework且需要最佳性能可以直接实例化此类。AesManaged一个完全用托管代码C#实现的AES。它的优点是跨平台一致性高不依赖特定操作系统API。但性能通常不如基于本地库的实现。在.NET Core之后除非有特殊原因否则优先使用Aes.Create()。AesCng基于Windows CNG (Cryptography Next Generation) API的实现是比CryptoAPI更新的技术。实操心得对于绝大多数现代.NET应用程序.NET Core 3.1, .NET 5/6/7/8直接使用Aes.Create()是最佳实践。框架会为你选择当前环境下最合适的实现保证了代码的简洁性和未来的兼容性。只有在你需要锁定特定实现如为了通过特定安全审计时才去直接实例化具体的类。3. 核心DEMOAESEncrypt类设计与实现理解了理论我们开始构建一个健壮、可复用的AESEncrypt工具类。这个类将封装加密、解密的完整流程并处理好密钥管理和异常。3.1 类结构设计与基础方法首先我们设计一个静态工具类提供字符串和字节数组两种形式的加密解密方法。using System; using System.IO; using System.Security.Cryptography; using System.Text; namespace YourNamespace.Security { /// summary /// AES加密解密工具类 /// /summary public static class AESEncrypt { // 默认的密钥和IV仅用于演示生产环境应从安全配置中读取 private static readonly byte[] DefaultKey Encoding.UTF8.GetBytes(Your32ByteKeyHere_123456789012); // 32字节 256位 private static readonly byte[] DefaultIV Encoding.UTF8.GetBytes(16ByteIVHere!!!); // 16字节 /// summary /// 使用默认密钥和IV加密字符串 /// /summary public static string Encrypt(string plainText) { return Encrypt(plainText, DefaultKey, DefaultIV); } /// summary /// 使用指定密钥和IV加密字符串 /// /summary public static string Encrypt(string plainText, byte[] key, byte[] iv) { if (string.IsNullOrEmpty(plainText)) throw new ArgumentNullException(nameof(plainText)); if (key null || key.Length 0) throw new ArgumentNullException(nameof(key)); if (iv null || iv.Length 0) throw new ArgumentNullException(nameof(iv)); byte[] encryptedBytes; using (Aes aesAlg Aes.Create()) { aesAlg.Key key; aesAlg.IV iv; // 显式设置模式和填充虽然CBC和PKCS7是默认值但显式设置是良好习惯 aesAlg.Mode CipherMode.CBC; aesAlg.Padding PaddingMode.PKCS7; ICryptoTransform encryptor aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); using (MemoryStream msEncrypt new MemoryStream()) { using (CryptoStream csEncrypt new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { using (StreamWriter swEncrypt new StreamWriter(csEncrypt, Encoding.UTF8)) { swEncrypt.Write(plainText); } } encryptedBytes msEncrypt.ToArray(); } } // 将字节数组转换为Base64字符串便于存储和传输如放在URL或JSON中 return Convert.ToBase64String(encryptedBytes); } /// summary /// 使用默认密钥和IV解密字符串 /// /summary public static string Decrypt(string cipherText) { return Decrypt(cipherText, DefaultKey, DefaultIV); } /// summary /// 使用指定密钥和IV解密字符串 /// /summary public static string Decrypt(string cipherText, byte[] key, byte[] iv) { if (string.IsNullOrEmpty(cipherText)) throw new ArgumentNullException(nameof(cipherText)); if (key null || key.Length 0) throw new ArgumentNullException(nameof(key)); if (iv null || iv.Length 0) throw new ArgumentNullException(nameof(iv)); string plaintext; byte[] cipherBytes Convert.FromBase64String(cipherText); // 从Base64解码回字节数组 using (Aes aesAlg Aes.Create()) { aesAlg.Key key; aesAlg.IV iv; aesAlg.Mode CipherMode.CBC; aesAlg.Padding PaddingMode.PKCS7; ICryptoTransform decryptor aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); using (MemoryStream msDecrypt new MemoryStream(cipherBytes)) { using (CryptoStream csDecrypt new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) { using (StreamReader srDecrypt new StreamReader(csDecrypt, Encoding.UTF8)) { plaintext srDecrypt.ReadToEnd(); } } } } return plaintext; } } }代码解析与要点编码一致性注意在StreamWriter和StreamReader中我们都指定了Encoding.UTF8。这是为了确保字符串与字节流转换时编码一致否则可能会出现乱码。Base64编码加密输出的是字节数组直接存储为二进制不方便。转换为Base64字符串是通用做法可以安全地放入JSON、XML或URL需URL编码中。using语句Aes、CryptoStream、MemoryStream等都实现了IDisposable接口。使用using语句确保即使在发生异常时这些非托管资源也能被及时释放这是编写健壮加密代码的基本要求。参数校验对输入参数进行空值或长度检查可以提前抛出清晰的异常避免后续更隐晦的错误。3.2 进阶功能生成随机密钥与IV硬编码密钥和IV是极其危险的做法。在实际项目中密钥应该从安全的配置源如Azure Key Vault、HashiCorp Vault、或受保护的环境变量获取而IV则应该在每次加密时随机生成。public static class AESEncrypt { // ... 之前的加密解密方法 ... /// summary /// 生成一个指定长度的随机密钥128, 192, 256位 /// /summary /// param namekeySizeInBits密钥长度必须是128, 192, 256之一/param /// returns随机密钥的字节数组/returns public static byte[] GenerateRandomKey(int keySizeInBits 256) { if (keySizeInBits ! 128 keySizeInBits ! 192 keySizeInBits ! 256) throw new ArgumentException(Key size must be 128, 192, or 256 bits., nameof(keySizeInBits)); using (Aes aes Aes.Create()) { aes.KeySize keySizeInBits; // 设置密钥长度 aes.GenerateKey(); // 生成随机密钥 return aes.Key; // 返回生成的密钥 } } /// summary /// 生成一个随机的初始化向量 (IV) /// /summary /// returns16字节的随机IV/returns public static byte[] GenerateRandomIV() { using (Aes aes Aes.Create()) { aes.GenerateIV(); // 生成随机IV return aes.IV; // 返回生成的IV } } /// summary /// 加密并返回包含IV的完整结果常用模式IV密文 /// /summary public static byte[] EncryptWithIV(byte[] plainData, byte[] key) { using (Aes aes Aes.Create()) { aes.Key key; aes.GenerateIV(); // 每次加密生成新IV aes.Mode CipherMode.CBC; aes.Padding PaddingMode.PKCS7; using (ICryptoTransform encryptor aes.CreateEncryptor()) using (MemoryStream ms new MemoryStream()) { // 先将IV写入流的开头 ms.Write(aes.IV, 0, aes.IV.Length); // 再写入加密后的数据 using (CryptoStream cs new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) { cs.Write(plainData, 0, plainData.Length); cs.FlushFinalBlock(); } return ms.ToArray(); } } } /// summary /// 解密包含IV的完整数据 /// /summary public static byte[] DecryptWithIV(byte[] cipherDataWithIV, byte[] key) { using (Aes aes Aes.Create()) { aes.Key key; aes.Mode CipherMode.CBC; aes.Padding PaddingMode.PKCS7; // 从数据前16字节读取IV byte[] iv new byte[16]; Buffer.BlockCopy(cipherDataWithIV, 0, iv, 0, iv.Length); aes.IV iv; // 剩余部分是真正的密文 int cipherDataLength cipherDataWithIV.Length - iv.Length; byte[] cipherData new byte[cipherDataLength]; Buffer.BlockCopy(cipherDataWithIV, iv.Length, cipherData, 0, cipherDataLength); using (ICryptoTransform decryptor aes.CreateDecryptor()) using (MemoryStream ms new MemoryStream(cipherData)) using (CryptoStream cs new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) using (MemoryStream output new MemoryStream()) { cs.CopyTo(output); return output.ToArray(); } } } }为什么推荐“IV密文”模式这种方式将IV和密文捆绑在一起简化了存储和传输逻辑。解密方只需要拿到这个组合体和密钥就能正确解密无需额外传递IV。这是一种非常常见的实践。4. 实战场景与高级配置一个DEMO要真正有用必须能应对真实场景。下面我们探讨几个常见需求及其实现。4.1 场景一加密配置文件中的连接字符串假设你的appsettings.json里有一个数据库连接字符串你不想让它以明文形式存在。{ ConnectionStrings: { DefaultConnection: ServermyServer;DatabasemyDb;User IdmyUser;PasswordmyPassword; }, Encryption: { Key: Base64EncodedAESKeyHere..., // 从安全的地方注入如环境变量 IV: Base64EncodedAESIVHere... // 可选如果使用固定IV } }你可以创建一个配置处理器using Microsoft.Extensions.Configuration; using System; public class EncryptedConfigurationProvider { private readonly IConfiguration _configuration; private readonly byte[] _key; private readonly byte[] _iv; public EncryptedConfigurationProvider(IConfiguration configuration) { _configuration configuration; // 从配置中读取Base64编码的密钥和IV并解码 string keyBase64 _configuration[Encryption:Key]; string ivBase64 _configuration[Encryption:IV]; // 如果使用固定IV if (string.IsNullOrEmpty(keyBase64)) throw new InvalidOperationException(Encryption key is not configured.); _key Convert.FromBase64String(keyBase64); _iv string.IsNullOrEmpty(ivBase64) ? null : Convert.FromBase64String(ivBase64); } public string GetDecryptedConnectionString() { string encryptedConnString _configuration.GetConnectionString(DefaultConnection); if (string.IsNullOrEmpty(encryptedConnString)) return null; // 如果配置中存储的是加密后的Base64字符串 if (_iv ! null) { // 使用固定IV解密 return AESEncrypt.Decrypt(encryptedConnString, _key, _iv); } else { // 更安全的方式连接字符串本身是“IV密文”的Base64组合 byte[] combinedData Convert.FromBase64String(encryptedConnString); byte[] decryptedData AESEncrypt.DecryptWithIV(combinedData, _key); return Encoding.UTF8.GetString(decryptedData); } } }注意事项永远不要将密钥硬编码在源代码或提交到版本库中。应该使用像Azure Key Vault、AWS Secrets Manager或至少是环境变量、在CI/CD流水线中注入的方式来管理密钥。配置文件中的Key和IV值也应该来自这些安全源。4.2 场景二选择不同的加密模式和填充.NET的Aes类支持多种模式和填充。以下是如何使用新的静态方法.NET 5进行更简洁的操作public static byte[] EncryptCbc(byte[] plainData, byte[] key, byte[] iv) { using (Aes aes Aes.Create()) { aes.Key key; // 使用静态方法更高效避免额外的流操作 return aes.EncryptCbc(plainData, iv, PaddingMode.PKCS7); } } public static byte[] DecryptCbc(byte[] cipherData, byte[] key, byte[] iv) { using (Aes aes Aes.Create()) { aes.Key key; return aes.DecryptCbc(cipherData, iv, PaddingMode.PKCS7); } } // 使用ECB模式再次警告仅用于理解生产环境避免使用 public static byte[] EncryptEcb(byte[] plainData, byte[] key) { using (Aes aes Aes.Create()) { aes.Key key; aes.Mode CipherMode.ECB; // 显式设置为ECB aes.Padding PaddingMode.PKCS7; // 注意ECB模式不需要IV return aes.EncryptEcb(plainData, PaddingMode.PKCS7); } }模式选择指南CBC通用场景首选。需要IV安全性好。ECB绝不用于加密有意义的数据。它仅适用于加密完全随机的、独立的数据块如加密其他密钥。CFB/OFB当需要将分组密码当流密码用或者需要实时加密如音视频流时考虑。在.NET中使用EncryptCfb/DecryptCfb方法时需要指定feedbackSizeInBits参数通常为8或128。4.3 场景三处理大文件流式加密当需要加密大文件如几百MB的日志或媒体文件时不能一次性将整个文件读入内存。这时需要使用流式处理。public static void EncryptFile(string inputFilePath, string outputFilePath, byte[] key, byte[] iv) { using (Aes aes Aes.Create()) { aes.Key key; aes.IV iv; using (ICryptoTransform encryptor aes.CreateEncryptor()) using (FileStream inputStream File.OpenRead(inputFilePath)) using (FileStream outputStream File.Create(outputFilePath)) using (CryptoStream cryptoStream new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write)) { // 使用缓冲区分块读取和加密 byte[] buffer new byte[81920]; // 80KB缓冲区 int bytesRead; while ((bytesRead inputStream.Read(buffer, 0, buffer.Length)) 0) { cryptoStream.Write(buffer, 0, bytesRead); } // CryptoStream在Dispose时会自动调用FlushFinalBlock } } } public static void DecryptFile(string inputFilePath, string outputFilePath, byte[] key, byte[] iv) { using (Aes aes Aes.Create()) { aes.Key key; aes.IV iv; using (ICryptoTransform decryptor aes.CreateDecryptor()) using (FileStream inputStream File.OpenRead(inputFilePath)) using (CryptoStream cryptoStream new CryptoStream(inputStream, decryptor, CryptoStreamMode.Read)) using (FileStream outputStream File.Create(outputFilePath)) { byte[] buffer new byte[81920]; int bytesRead; while ((bytesRead cryptoStream.Read(buffer, 0, buffer.Length)) 0) { outputStream.Write(buffer, 0, bytesRead); } } } }关键点CryptoStream像一个管道你向它写入数据它自动加密后传递给底层流加密时或者从它读取数据它自动从底层流读取并解密解密时。这种方式内存占用恒定与文件大小无关。5. 常见问题、性能优化与安全加固即使代码写对了在实际部署和运行时还是会遇到各种问题。下面是我踩过的一些坑和总结的经验。5.1 典型异常与排查异常信息可能原因解决方案CryptographicException: Padding is invalid and cannot be removed.最常见错误。1) 密钥或IV错误。2) 加密和解密时使用的模式或填充方式不一致。3) 密文在传输/存储过程中被损坏如Base64解码错误。4) 使用CBC模式时IV不正确或丢失。1) 仔细核对密钥和IV的字节数组是否完全一致。2) 确保Mode和Padding属性在加密解密时设置相同。3) 检查Base64字符串是否被截断或包含非法字符。4) 确保IV被正确传递和使用。ArgumentNullException传递给加密/解密方法的参数如明文、密钥、IV为null或空。在方法开始处添加参数校验。CryptographicException: Specified key is not a valid size for this algorithm.提供的密钥字节数组长度不符合AES要求必须是16, 24, 32字节对应128, 192, 256位。检查密钥生成或加载逻辑。使用Aes类的ValidKeySize方法或直接检查byte[]长度。CryptographicException: Length of the data to decrypt is invalid.密文长度不是块大小16字节的整数倍在使用某些填充模式如None时。或者密文本身已损坏。确保使用正确的填充模式如PKCS7。检查密文完整性。调试技巧遇到Padding is invalid错误时可以尝试以下步骤打印或记录下加密时使用的Key、IV、Mode、Padding的值。在解密前同样打印这些参数确保它们完全匹配。检查密文字节数组的长度。使用CBC模式PKCS7填充时密文长度一定是16的倍数。如果涉及网络传输确保接收端收到的Base64字符串与发送端完全一致无空格、换行符干扰。5.2 性能考量与最佳实践重用Aes实例创建Aes实例和ICryptoTransform加密器/解密器是有开销的。如果你需要在短时间内加密大量小数据块考虑创建一个Aes实例和对应的ICryptoTransform并重用它们。但要注意线程安全或者使用ThreadLocalT。使用Span和静态方法在.NET Core 3.0和.NET 5中Aes类提供了基于Spanbyte的静态方法如EncryptCbc,DecryptEcb。这些方法避免了流和临时缓冲区的分配性能更高是处理内存中字节数组的首选。// 高性能加密示例 byte[] plainData Encoding.UTF8.GetBytes(Hello, World!); byte[] key ...; byte[] iv ...; using (Aes aes Aes.Create()) { aes.Key key; // 预计算密文长度分配精确内存 int cipherLength aes.GetCiphertextLengthCbc(plainData.Length, PaddingMode.PKCS7); byte[] cipherData new byte[cipherLength]; // 使用Span进行加密零额外分配 int bytesWritten aes.EncryptCbc(plainData, iv, cipherData, PaddingMode.PKCS7); // cipherData 现在包含密文 }密钥派生如果你的密钥来源于一个密码如用户输入的密码不要直接使用密码的字节作为密钥。应该使用密钥派生函数KDF如PBKDF2在.NET中是Rfc2898DeriveBytes来生成一个强密钥。public static byte[] DeriveKeyFromPassword(string password, byte[] salt, int keySizeInBytes 32) { using (var deriveBytes new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256)) { return deriveBytes.GetBytes(keySizeInBytes); // 32字节 256位 } }5.3 安全加固要点密钥管理是重中之重再次强调密钥的安全存储和分发是整个加密体系中最脆弱的一环。使用专业的密钥管理服务KMS。使用认证加密标准的AES-CBC只能保证机密性不能保证完整性即攻击者可能篡改密文导致解密出乱码但系统无法察觉。对于高安全要求场景应考虑使用认证加密模式如AES-GCMGalois/Counter Mode。在.NET中可以通过AesGcm类位于System.Security.Cryptography命名空间但注意它通常只在.NET Core 3.0的非浏览器环境中可用来实现。AES-GCM同时提供加密和身份验证标签能有效防止密文被篡改。避免时序攻击比较密钥或密文时使用恒定时间比较算法如CryptographicOperations.FixedTimeEquals而不是简单的操作符以防止通过比较时间差来推测信息。及时清理内存中的敏感数据密钥、明文等敏感数据在字节数组中使用完后应尽快用随机数据覆盖或使用Array.Clear()清空防止它们长期驻留在内存中被窃取。6. 从DEMO到生产构建健壮的加密服务最后我们将这个DEMO升级为一个更适合生产环境的、可测试的加密服务。using System; using System.Security.Cryptography; using Microsoft.Extensions.Options; namespace YourNamespace.Services { public interface IAesEncryptionService { string EncryptToBase64(string plainText); string DecryptFromBase64(string cipherText); byte[] Encrypt(byte[] plainData); byte[] Decrypt(byte[] cipherData); } public class AesEncryptionOptions { public string Base64Key { get; set; } // 从安全配置注入 public CipherMode Mode { get; set; } CipherMode.CBC; public PaddingMode Padding { get; set; } PaddingMode.PKCS7; } public class AesEncryptionService : IAesEncryptionService, IDisposable { private readonly byte[] _key; private readonly CipherMode _mode; private readonly PaddingMode _padding; private readonly Aes _aes; public AesEncryptionService(IOptionsAesEncryptionOptions options) { if (options null) throw new ArgumentNullException(nameof(options)); var opts options.Value; _key Convert.FromBase64String(opts.Base64Key ?? throw new ArgumentException(Key must be configured.)); _mode opts.Mode; _padding opts.Padding; // 创建并配置一个长期存在的Aes实例注意线程安全 _aes Aes.Create(); _aes.Key _key; _aes.Mode _mode; _aes.Padding _padding; } public string EncryptToBase64(string plainText) { if (string.IsNullOrEmpty(plainText)) return plainText; byte[] plainData Encoding.UTF8.GetBytes(plainText); byte[] cipherData Encrypt(plainData); return Convert.ToBase64String(cipherData); } public string DecryptFromBase64(string cipherText) { if (string.IsNullOrEmpty(cipherText)) return cipherText; byte[] cipherData Convert.FromBase64String(cipherText); byte[] plainData Decrypt(cipherData); return Encoding.UTF8.GetString(plainData); } public byte[] Encrypt(byte[] plainData) { if (plainData null || plainData.Length 0) return plainData; // 每次加密生成新的随机IV _aes.GenerateIV(); byte[] iv _aes.IV; using (ICryptoTransform encryptor _aes.CreateEncryptor(_aes.Key, iv)) using (MemoryStream ms new MemoryStream()) { // 写入IV ms.Write(iv, 0, iv.Length); // 写入密文 using (CryptoStream cs new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) { cs.Write(plainData, 0, plainData.Length); cs.FlushFinalBlock(); } return ms.ToArray(); } } public byte[] Decrypt(byte[] cipherDataWithIv) { if (cipherDataWithIv null || cipherDataWithIv.Length 16) // 至少包含IV throw new ArgumentException(Invalid cipher data., nameof(cipherDataWithIv)); // 提取IV前16字节 byte[] iv new byte[16]; Buffer.BlockCopy(cipherDataWithIv, 0, iv, 0, 16); _aes.IV iv; // 剩余部分是密文 int cipherLength cipherDataWithIv.Length - 16; byte[] cipherData new byte[cipherLength]; Buffer.BlockCopy(cipherDataWithIv, 16, cipherData, 0, cipherLength); using (ICryptoTransform decryptor _aes.CreateDecryptor(_aes.Key, _aes.IV)) using (MemoryStream ms new MemoryStream(cipherData)) using (CryptoStream cs new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) using (MemoryStream output new MemoryStream()) { cs.CopyTo(output); return output.ToArray(); } } public void Dispose() { _aes?.Dispose(); // 安全擦除内存中的密钥虽然托管环境不能完全保证 if (_key ! null) Array.Clear(_key, 0, _key.Length); } } }在Startup或Program中注册服务// .NET Core 依赖注入配置 services.ConfigureAesEncryptionOptions(Configuration.GetSection(AesEncryption)); services.AddSingletonIAesEncryptionService, AesEncryptionService();这个服务类的主要改进依赖注入通过IOptions模式配置密钥和算法参数解耦了配置和实现。资源管理实现了IDisposable确保Aes实例和密钥内存被妥善清理。线程安全注意这个简单实现中_aes实例的IV属性在Encrypt和Decrypt方法中被修改。如果在多线程环境下并发调用会导致IV混乱。生产环境中要么每次调用创建新的Aes实例性能有损耗要么使用线程本地存储ThreadLocalAes或者确保服务以单例方式运行但方法内部通过锁或其他同步机制保护对IV的修改。更简单的做法是不重用Aes实例的IV属性而是在加密方法内部创建新的Aes实例或使用静态方法这是最稳妥的。通过以上从理论到实践从基础DEMO到生产级服务的逐步拆解相信你已经对.NET中的AES加密解密有了全面而深入的理解。记住加密是一个系统工程选择正确的算法和参数只是第一步安全的密钥管理、完整的数据处理流程和对边界情况的防御才是构建真正安全应用的基石。