AES加解密必须动态生成随机唯一IV(16字节)并前置拼接密文;密钥须经PBKDF2(Rfc2898DeriveBytes)派生,配合随机salt;使用CryptoStream时需嵌套using确保FlushFinalBlock;本地密钥存储应依赖DPAPI而非自实现保护。

AES加解密必须自己管理IV,不能硬编码或复用
很多人一上来就用固定IV(比如全0数组)或直接把IV写死在代码里,结果是相同明文永远生成相同密文——这等于把AES降级成“可预测的凯撒密码”,完全失去语义安全。IV不是密钥,但必须随机、唯一、每次加密都重生成,并和密文一起保存(通常前置拼接)。
-
IV长度必须和AES块大小一致(128位,即16字节),用Aes.Create().BlockSize确认,别手算 - 生成用
RandomNumberGenerator.Fill()(.NET 6+),旧版本用RNGCryptoServiceProvider,别用new Random() - 解密时先从密文头部读出前16字节作为
IV,再用它初始化解密器——漏这步会抛CryptographicException: The input data is not a complete block - IV本身不保密,可以明文存,但绝不能和密钥混在一起硬编码进程序集
密钥不能直接用字符串,得走PBKDF2派生
用户口令(比如"myPass123")不能直接当AES密钥用。ASCII字符串只有不到7比特/字符熵,且长度不匹配(AES-256要32字节)。直接Encoding.UTF8.GetBytes("myPass123")生成的密钥强度极低,暴力破解几秒就破。
- 必须用
Rfc2898DeriveBytes做密钥派生:传入口令、盐值(salt)、迭代次数(建议≥100_000) - 盐值
salt要随机生成(同IV逻辑),长度≥16字节,且每次加密都换——和IV一样,拼在密文最前面 - 派生出的密钥长度必须严格匹配AES要求:
32字节对应AES-256,16对应AES-128;用deriver.GetBytes(32)取值,别截断或补零 - 别用MD5/SHA1做派生算法,
Rfc2898DeriveBytes默认是HMAC-SHA1,够用;若需更强,.NET 5+可用Rfc2898DeriveBytes配合HashAlgorithmName.SHA256
FileStream + CryptoStream组合时,务必用using嵌套且顺序不能错
常见错误是把CryptoStream和FileStream分开using,或者先Dispose了FileStream再让CryptoStream写——这会导致最后一块数据没刷盘,文件损坏或解密失败,报CryptographicException: Padding is invalid and cannot be removed。
- 必须用嵌套
using:外层FileStream,内层CryptoStream,确保CryptoStream.Dispose()触发FlushFinalBlock() - 写入时用
CryptoStream.Write(),别用WriteAsync除非你手动await cryptoStream.FlushFinalBlockAsync() - 读取时,
CryptoStream.Read()返回值可能小于请求长度,必须循环读直到返回0,否则可能漏最后几十字节 - 如果文件很大(>100MB),考虑分块处理(如每次读4KB),避免内存暴涨;但注意:AES-CBC模式下,块之间有依赖,不能随意切分——整个文件必须走同一个
CryptoStream
本地存储密钥材料时,别碰Windows DPAPI以外的“自实现保护”
有人试图把密钥加密后再存注册表或配置文件,结果用的还是自己写的简单XOR或Base64——这等于给保险柜贴张纸条写“钥匙在花盆底下”。真要保护密钥,就该交给系统。
- .NET自带
ProtectedData.Protect()(DPAPI),只在当前用户/机器上下文有效,无需额外密钥,调用简单:ProtectedData.Protect(keyBytes, null, DataProtectionScope.CurrentUser) - 别用
ProtectedMemory.Protect()——它只保护内存页,进程一退出就清空,对持久化密钥无意义 - 如果应用是服务(Service)或跨用户运行,DPAPI会失败,此时必须用外部密钥管理(如Azure Key Vault),别自己造轮子
- DPAPI保护后的密钥仍需存到安全位置:避免放App.config或exe同目录;推荐放
%LOCALAPPDATA%\YourApp\keys.bin,并设ACL仅当前用户可读
IV、salt、密文三者顺序和长度协议一旦定下就不能改,哪怕只是多写一个字节,解密就会彻底失败——这种细节没有错误提示,只会静默解出乱码。上线前务必用已知明文跑通全流程,再拿真实数据试。










