应使用 Aes.Create() 而非 new AesManaged(),因其已过时且不兼容新版本;必须正确处理 IV(随机生成、与密文共存)、密钥长度(128/192/256 位)、模式(禁用 ECB,推荐 CBC/GCM)及流式加解密以避免内存溢出。

加密时用 Aes.Create() 而不是 new AesManaged()
因为 AesManaged 已被标记为过时,.NET Core 3.0+ 和 .NET 5+ 默认不支持它,硬写会编译失败或运行时报 PlatformNotSupportedException。用 Aes.Create() 是跨平台、向后兼容的正确入口。
它返回的是具体实现(如 AesCryptoServiceProvider 或 AesCng),底层自动选最优——Windows 上优先 AesCng(性能好、支持硬件加速),Linux/macOS 上走 OpenSslAes。
- 别手动 new 具体类,除非你明确要锁死某实现(比如调试时强制用
AesCryptoServiceProvider) -
Aes.Create()返回的对象必须Dispose(),建议用using包裹 - 密钥长度必须是 128、192 或 256 位(即 16、24、32 字节),传错会抛
CryptographicException:“Specified key is not a valid size for this algorithm”
文件加密必须自己处理 IV 并和密文一起保存
AES 是分组密码,ECB 模式不安全,必须用 CBC 或 GCM;而 CBC 要求每次加密用唯一 IV(初始化向量),且解密时必须用**完全相同的 IV**。IV 不需要保密,但绝不能复用——同一密钥下重复 IV 会导致密文可预测,严重削弱安全性。
常见错误是把 IV 写死(如全 0 数组)或忽略保存,导致解密失败或报 CryptographicException:“The input data is not a complete block” 或 “Padding is invalid”。
- 加密前调用
aes.GenerateIV()生成随机 IV - 把 IV 写在密文开头(比如前 16 字节),解密时先读出这 16 字节作为 IV
- 不要用
Encoding.UTF8.GetBytes("my_iv_123")这种字符串转 IV 的方式——长度和随机性都不对 - GCM 模式还要额外处理认证标签(
Tag),比 CBC 更复杂,新手建议从 CBC 入手
解密失败大概率是密钥/IV/模式三者没对齐
加密和解密两端必须严格一致:同样的密钥字节数组、同样的 IV 字节数组、同样的 Mode(如 CipherMode.CBC)、同样的 Padding(如 PaddingMode.PKCS7)。哪怕只差一个字节,也会直接抛异常,而不是“解出来乱码”。
典型现象:CryptographicException 提示 “Bad Data”、“Padding is invalid and cannot be removed” 或 “Length of the data to decrypt is invalid”。这不是算法问题,是参数没配平。
- 检查密钥是否用了相同派生方式(比如都用
Rfc2898DeriveBytes+ 相同 salt 和 iteration count) - 确认加密和解密代码里
aes.Mode = CipherMode.CBC和aes.Padding = PaddingMode.PKCS7都显式设置了 - 如果密钥来自口令(password),务必保证加盐(salt)值也一并保存并复用——salt 不等于 IV,两者都要存
- 别用
Convert.ToBase64String(key)后又用Encoding.UTF8.GetBytes()反解——Base64 解码要用Convert.FromBase64String()
大文件别一次性读进内存,用流式加解密
用 File.ReadAllBytes() 加密 500MB 文件,会瞬间分配 500MB 内存,还可能触发 GC 压力或 OutOfMemoryException。AES 本身支持流式处理,.NET 的 ICryptoTransform 就是为这个设计的。
核心是:用 CryptoStream 包裹 FileStream,边读边算,内存占用稳定在几 KB。
- 加密:打开源文件
FileStream(FileMode.Open),创建目标文件FileStream(FileMode.Create),再套一层CryptoStream写入 - 解密:反过来,目标
FileStream作为输入,源FileStream作为输出 - 注意
CryptoStream的FlushFinalBlock()必须调用(using会自动做),否则最后一块 PKCS7 填充不生效,解密报错 - 别在
CryptoStream上调用Seek()或反复读——它不支持随机访问


















