aes在c#中必须严格匹配密钥(128/192/256位)、iv(16字节)、ciphermode和paddingmode,否则直接抛cryptographicexception;应统一使用aes.create()、避免硬编码密钥、优先选用aesgcm(.net core 3.0+)并正确处理nonce与tag。

AES在C#里不是“能跑就行”,而是密钥、IV、模式、填充四者必须严丝合缝,差一个字节就抛 CryptographicException,不是乱码,是直接失败。
用 Aes.Create() 而不是 AesManaged 或 RijndaelManaged
前者已过时,.NET Core 3.0+ 和所有新版 .NET(包括 .NET 6/7/8/9)中调用 new AesManaged() 会直接报 PlatformNotSupportedException;RijndaelManaged 虽能运行但不推荐——它不限制块大小,可能意外启用非标准模式。
正确做法是统一用 Aes.Create():
- 返回具体实现(Windows 上通常是
AesCng,Linux/macOS 是OpenSslAes),性能和安全性都有保障 - 必须用
using包裹,否则底层加密句柄泄漏 - 别手动 new
AesCryptoServiceProvider——除非你明确要绕过自动选型做调试
密钥长度和 IV 必须严格匹配 AES 要求
AES 只接受 128 / 192 / 256 位密钥(即 16 / 24 / 32 字节),IV 固定为 16 字节。传错会立刻抛 CryptographicException: "Specified key is not a valid size for this algorithm"。
常见错误写法:
-
Encoding.UTF8.GetBytes("mykey123")→ 长度 8 字节,不够 16,且无随机性 -
new byte[16]全零 IV → 每次加密结果可预测,严重削弱安全性 - 把口令字符串直接当密钥用 → 缺少派生,等同于裸奔
安全做法:
- 密钥从口令派生:用
Rfc2898DeriveBytes(PBKDF2)生成 32 字节密钥 + 16 字节 IV - 或加密前调用
aes.GenerateIV()随机生成 IV,并与密文一起保存(如前置到输出流开头) - 硬编码密钥仅限测试,生产环境必须走密钥管理服务或环境变量注入
解密失败几乎全是参数没对齐,不是算法问题
典型异常:"Padding is invalid and cannot be removed"、"The input data is not a complete block"、"Bad Data" —— 这些都不是“解错了”,而是密钥、IV、CipherMode、PaddingMode 四者中至少有一项不一致。
检查清单:
- 加密和解密是否用了完全相同的
byte[]密钥(不是相同字符串再转一次) - IV 是否从密文头准确提取(比如读前 16 字节),而不是重新生成或截断
- 两端
aes.Mode是否都设为CipherMode.CBC(或都为GCM) - 两端
aes.Padding是否都为PaddingMode.PKCS7(.NET 默认,但显式设置更稳妥) - 文件加解密时,是否误把 UTF-8 BOM 当作密文一部分读入?建议用
BinaryReader而非StreamReader处理原始字节
AesGcm 是更现代的选择,但只支持 .NET Core 3.0+ / .NET 5+
如果你的运行时满足条件,AesGcm 应优先于 CBC:它自带认证标签(Tag),解密时校验失败直接抛异常,杜绝密文被篡改后静默解出垃圾数据的风险。
注意点:
- Nonce(类似 IV)必须是 12 字节,不是 16 字节;Tag 固定 16 字节
- 加密输出 = 密文 + Tag,Nonce 不嵌入输出,需单独传输或存储
- 解密时必须同时提供原始 Nonce 和收到的 Tag,缺一不可
- 不用手动处理填充,
AesGcm内部全托管
最易被忽略的是:GCM 模式下,Encrypt() 和 Decrypt() 方法签名与 CBC 完全不同,不能套用老代码逻辑——尤其是 Tag 的读写位置和长度校验,错一点就 ArgumentException。










