RSA 这个算法有个硬伤——它根本没法直接加密长字符串。明文长度受密钥位数严格限制,比如 2048 位的 RSA,最多只能啃下 245 字节(PKCS#1 v1.5 填充)或 190 字节(OAEP 填充),一旦超限就会抛出 CryptographicException。相比之下,AES 才是处理大块数据的正经选手。所以工业界的标准做法一直是:用 RSA 加密 AES 的密钥,再用 AES 加密实际数据——也就是所谓的“混合加密”。

一句话总结:AES 和 RSA 不能混着用同一个场景直接加密数据;AES 适合加密大量内容,RSA 只能加密很短的字节(比如 AES 密钥),否则你会撞上 CryptographicException: Bad Data 或 KeySize too small 这类错误。
为什么不能直接用 RSA 加密长字符串
RSA 是非对称算法,它的加密长度受密钥长度死死卡住。举个例子:2048 位的 RSA,最多只能加密 245 字节(PKCS#1 v1.5)或 190 字节(OAEP)的明文。超出这个范围?直接炸,CryptographicException 伺候。现实业务中,你要传输的 JSON、XML 或者用户输入,随随便便就几百上千字节,硬塞进去只会让解密端一脸懵。
- 用
RSACryptoServiceProvider或RSA.Create()加密前,务必先检查明文字节数:Encoding.UTF8.GetBytes(input).Length - 2048 位密钥对应最大明文 ≈ 245 字节;3072 位 ≈ 372 字节;但性能会明显下降,不建议盲目提升密钥位数
- 常见翻车现场:把整个 HTTP body 丢给
RSA.Encrypt(),结果运行时解密端直接报错
AES 加密必须配对使用 Encryptor/Decryptor 实例
同一个 Aes 实例,调用 CreateEncryptor() 和 CreateDecryptor() 才能保证 IV 和 Key 内部状态完全一致。跨实例、跨方法、甚至跨线程复用,解密时会报 CryptographicException: The input data is not a complete block,或者给你一堆乱码。
- IV 必须是 16 字节,而且每次加密都应该随机生成(
Aes.GenerateIV()),千万别复用或硬编码 - 密文结构建议采用:
[IV][AES ciphertext],解密时先取前 16 字节作 IV,再用剩余部分解密 - 别再用
AesManaged(已过时),改用Aes.Create()—— 它默认启用硬件加速,而且更安全
RSA + AES 混合加密的正确组装方式
标准流程是这样的:先用 AES 加密原始数据,再用 RSA 公钥加密 AES 的 Key(注意不是 IV),最后把加密后的 AES 密钥 + IV + AES 密文打包发送。接收方先用 RSA 私钥解出 AES Key,再用这个 Key 加上 IV 去解 AES 密文。
- 不要用 RSA 加密 IV —— IV 本来就是公开的,随密文一起传就行
- AES Key 必须是随机生成的,严禁用密码派生(比如 PBKDF2)后硬编码进程序;应该由
Aes.Key自动生成 - RSA 解密端必须确保私钥可导出(
X509KeyStorageFlags.Exportable),否则ExportParameters(true)会抛出Key not valid for use in specified state - 参考代码片段:
byte[] aesKey = aes.Key; // 随机生成
byte[] encryptedAesKey = rsa.Encrypt(aesKey, RSAEncryptionPadding.OaepSHA256);
MD5、DES 这些算法现在还能用吗
答案很明确:不能再用于安全敏感场景。MD5 已经被证明可以碰撞,DES 密钥太短(56 位),DES 和 TripleDES 在 .NET 6+ 中已经被标记为 [Obsolete],运行时会出现警告;MD5CryptoServiceProvider 同样过时。
- 密码存储必须用
Rfc2898DeriveBytes(即 PBKDF2)+ 盐值,而不是 MD5 哈希 - 完整性校验改用
SHA256或SHA512,别再用MD5.ComputeHash() - 如果遗留系统强制要求 DES 兼容,至少用
TripleDES并确保模式为CBC、填充为PKCS7,但强烈建议推动升级到 AES
最容易被忽略的细节是:AES 的 Mode 和 Padding 必须在两端完全一致(比如都设为 CipherMode.CBC 和 PaddingMode.PKCS7),哪怕只差一个枚举值,解密就会静默失败或者抛异常。别依赖默认值,显式设置才是王道。