前言
欢迎关注dotnet研习社,今天我们讨论的主题是“非对称加密(RSA)的一个245异常解决方案”。
很多刚接触非对称加密(RSA)的同学,都踩过一个坑:
我用 RSA 加密一段数据,却抛出异常:
System.Security.Cryptography.CryptographicException: The message exceeds the maximum allowable length for the chosen options (245).
疑问立刻就来了:
- RSA 是非对称加密,号称安全性高,那为什么不能随便加密大数据?
- 245 这个数字又是怎么来的?
- 想要加密大文件或长文本怎么办?
别急,这篇文章一次性帮你彻底解惑。
一、RSA 是什么?
RSA 是世界上最经典、应用最广泛的非对称加密算法之一。
它最大的特点是:
- 用一对密钥:公钥 和 私钥
- 加密和解密使用不同的密钥
- 一个加密后,只能用另一个解密
因为这种「非对称性」,RSA 成为 HTTPS、SSL/TLS、数字签名、区块链等场景的基石。
二、RSA 能加密多少字节?
很多人以为:
RSA 是加密算法,那我可以直接用它加密 1MB 的文件。
实际上这是完全错误的!
RSA 的原理是大数分解,本质上是对一个大整数做模运算。所以:
- 加密时输入的「明文」也会被看作一个大整数
- 这个整数必须小于模数(n)
- 而模数的大小就是你密钥长度决定的
举个例子:
- 常用 RSA 密钥长度是 2048 位 = 256 字节
- 加密时还要预留填充(Padding)用于增强安全性(比如 PKCS#1 v1.5 填充至少要 11 字节)
所以:
可加密最大明文 ≈ 256 字节 - 11 字节 = 245 字节
这就是那个神秘的 245 的来历。
三、为什么不能直接分块?
有的同学会问:
那我把文件拆成 245 字节一块一块 RSA 加密可不可以?
理论上可行,实际上极不推荐!
原因:
- 性能差:RSA 加密本身速度慢,大文件分块会极大增加 CPU 消耗。
- 安全性弱:RSA 的分块加密容易被重放或分块攻击,不安全。
- 没有意义:工业标准里从来不是这么做的。
四、正确姿势:混合加密
那么怎么安全且高效地加密大数据?
正确方法是混合加密:
- 用 RSA 保护对称加密的密钥(如 AES Key)
- 用 AES 之类的对称加密算法处理大数据
核心思路:
- 对称加密快,适合大数据
- 非对称加密安全,适合保护密钥
两者组合,完美取长补短。
五、示例流程
- RSA 公钥加密 AES Key
- AES 加密大数据
- RSA 私钥解密 AES Key
- AES 解密还原大数据
完整示例:RSA + AES 混合加密
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace HybridEncryptionDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== Hybrid Encryption Demo ===");
// ===== 1. 生成 RSA 密钥对 =====
using RSA rsa = RSA.Create(2048);
var publicKey = rsa.ExportParameters(false);
var privateKey = rsa.ExportParameters(true);
// ===== 2. 原文数据 =====
string plainText = "";
plainText += "这是要加密的一段很长很长的数据,用于演示超过 RSA 245 字节限制,看看是否正常。X1 \r\n";
plainText += "这是要加密的一段很长很长的数据,用于演示超过 RSA 245 字节限制,看看是否正常。X2 \r\n";
plainText += "这是要加密的一段很长很长的数据,用于演示超过 RSA 245 字节限制,看看是否正常。X3 \r\n";
plainText += "这是要加密的一段很长很长的数据,用于演示超过 RSA 245 字节限制,看看是否正常。X4 \r\n";
plainText += "这是要加密的一段很长很长的数据,用于演示超过 RSA 245 字节限制,看看是否正常。X5 \r\n";
plainText += "这是要加密的一段很长很长的数据,用于演示超过 RSA 245 字节限制,看看是否正常。X6 \r\n";
plainText += "这是要加密的一段很长很长的数据,用于演示超过 RSA 245 字节限制,看看是否正常。X7 \r\n";
plainText += "这是要加密的一段很长很长的数据,用于演示超过 RSA 245 字节限制,看看是否正常。X8 \r\n";
plainText += "这是要加密的一段很长很长的数据,用于演示超过 RSA 245 字节限制,看看是否正常。X9 \r\n";
Console.WriteLine($"原文: {plainText},长度:{plainText.Length}");
// ===== 3. 生成 AES 密钥和 IV =====
using Aes aes = Aes.Create();
aes.KeySize = 256;
aes.GenerateKey();
aes.GenerateIV();
byte[] aesKey = aes.Key;
byte[] aesIV = aes.IV;
// ===== 4. 用 AES 加密大数据 =====
byte[] encryptedData = AESEncrypt(plainText, aesKey, aesIV);
Console.WriteLine($"AES 加密后: {Convert.ToBase64String(encryptedData)}");
// ===== 5. 用 RSA 公钥加密 AES Key + IV =====
using RSA rsaEncryptor = RSA.Create();
rsaEncryptor.ImportParameters(publicKey);
byte[] encryptedAesKey = rsaEncryptor.Encrypt(aesKey, RSAEncryptionPadding.Pkcs1);
byte[] encryptedAesIV = rsaEncryptor.Encrypt(aesIV, RSAEncryptionPadding.Pkcs1);
Console.WriteLine($"RSA 加密的 AES Key: {Convert.ToBase64String(encryptedAesKey)}");
Console.WriteLine($"RSA 加密的 AES IV: {Convert.ToBase64String(encryptedAesIV)}");
// ===== 6. 用 RSA 私钥解密 AES Key + IV =====
using RSA rsaDecryptor = RSA.Create();
rsaDecryptor.ImportParameters(privateKey);
byte[] decryptedAesKey = rsaDecryptor.Decrypt(encryptedAesKey, RSAEncryptionPadding.Pkcs1);
byte[] decryptedAesIV = rsaDecryptor.Decrypt(encryptedAesIV, RSAEncryptionPadding.Pkcs1);
// ===== 7. 用解密后的 AES Key 解密数据 =====
string decryptedText = AESDecrypt(encryptedData, decryptedAesKey, decryptedAesIV);
Console.WriteLine($"最终解密还原: {decryptedText}");
}
static byte[] AESEncrypt(string plainText, byte[] key, byte[] iv)
{
using Aes aesAlg = Aes.Create();
aesAlg.Key = key;
aesAlg.IV = iv;
using MemoryStream msEncrypt = new MemoryStream();
using CryptoStream csEncrypt = new CryptoStream(msEncrypt, aesAlg.CreateEncryptor(), CryptoStreamMode.Write);
using StreamWriter swEncrypt = new StreamWriter(csEncrypt);
swEncrypt.Write(plainText);
swEncrypt.Close();
return msEncrypt.ToArray();
}
static string AESDecrypt(byte[] cipherText, byte[] key, byte[] iv)
{
using Aes aesAlg = Aes.Create();
aesAlg.Key = key;
aesAlg.IV = iv;
using MemoryStream msDecrypt = new MemoryStream(cipherText);
using CryptoStream csDecrypt = new CryptoStream(msDecrypt, aesAlg.CreateDecryptor(), CryptoStreamMode.Read);
using StreamReader srDecrypt = new StreamReader(csDecrypt);
return srDecrypt.ReadToEnd();
}
}
}
运行效果
执行后,输出结果:
核心点
✅ 大数据用 AES 加密
✅ 用 RSA 公钥只加密 AES Key/IV
✅ 私钥解密后还原 Key/IV,再解密大数据
六、为什么要填充(Padding)?
还有人疑惑:
填充是干什么的?为什么要减掉 11 字节?
这是为了防止已知明文攻击。RSA 原生是纯数学运算,如果没有填充,同一个明文每次加密结果都一样,极易被穷举。
Padding(如 PKCS#1、OAEP)可以引入随机性,让同一个明文每次加密结果都不同,大大提升安全性。
七、一句话总结
✅ RSA 是用来加密小块数据的(比如对称密钥、签名、哈希)
✅ 大文件要用对称加密(AES、DES)
✅ 正确做法是混合加密
关于RSA算法,有一句快速记忆的口诀:
✅ 加密用公钥,解密用私钥
✅ 签名用私钥,验签用公钥
结语
当你再次看到:
The message exceeds the maximum allowable length for the chosen options (245).
不要惊慌,别想着把密钥换得更大,更不要分块暴力加密。
正确解法就是:
RSA + AES 混合加密
这才是安全通信的业界标配。