RSACryptoServiceProvider:字符串RSA加密的四条铁律
RSA加密需严守四要点:明文≤117字节(UTF-8)、密钥XML节点完整且无换行、填充模式加解密一致(false为PKCS#1 v1.5)、Base64编解码不可混淆字节与字符串。

在 .NET 里用 RSACryptoServiceProvider 对字符串做 RSA 加密,可不像调用一个 Encrypt 方法那么简单。很多开发者遇到的坑,九成以上都绕不开这四个问题:明文超长、编码不一致、密钥格式错误,以及忽略了填充模式。所以,先别急着写代码,得搞清楚你面对的是哪种情况。
明文长度超过 128 - 11 = 117 字节时会抛出 CryptographicException
RSA 加密有个硬性门槛:使用 1024 位密钥时,明文最大长度是 117 字节(如果采用 UTF-8 编码,实际能容纳的字符数可能更少)。这可不是什么程序缺陷,而是 PKCS#1 v1.5 填充规范强制要求的预留空间。一旦 Encoding.UTF8.GetBytes(content).Length > 117,Encrypt 方法就会立刻抛出异常,错误信息通常是 CryptographicException: Bad Length,有时也会误报为 Key does not exist。
- 核心原则:别拿 RSA 去加密长文本。它本就不是干这个的——它的正确用途是加密对称密钥(比如 AES 的 32 字节密钥)或者短令牌。
- 如果必须处理大文本:那就得分块。手动将明文切分成 ≤117 字节的小块,逐块加密,最后把每块加密结果的 Base64 字符串拼接起来。需要注意的是,
RSACryptoServiceProvider本身不支持自动分块,这个流程得自己控制。 - 更推荐的方案:采用混合加密。用 AES 加密你的原始长文本,然后用 RSA 去加密 AES 的
Key和IV,最后将这两部分组合起来传输。这才是标准做法。
FromXmlString 加载密钥失败的常见原因
密钥 XML 格式不对、文件权限不足,或者 XML 里缺失了必要的节点,都会导致 FromXmlString 方法静默失败,或者抛出 CryptographicException: Key not valid for use in specified state 这种让人摸不着头脑的异常。
- XML 节点必须完整:公钥 XML 必须包含
和节点;私钥则在此基础上,还必须包含、、、、和这些节点,缺一不可。 - 别手动拼接 XML:尤其要注意,密钥 Base64 值里的换行符(\r\n)会导致解析失败。最稳妥的办法是,直接用
rsa.ToXmlString(true/false)方法生成的 XML 字符串原样保存和使用。 - 注意平台兼容性:在 .NET Core / .NET 5+ 中,
RSACryptoServiceProvider已被标记为过时。新项目应该改用RSA.Create()配合ImportFromPem或ImportParameters方法,否则在 Linux 容器等环境下,密钥加载失败的概率会大大增加。
Encrypt 第二个参数设为 false 意味着使用 PKCS#1 v1.5 填充
这里有个关键误解:Encrypt 方法的第二个布尔参数,不是决定“要不要填充”,而是指定“用哪种填充方案”。false 对应 PKCS#1 v1.5,true 则对应 OAEP。这两种方案互不兼容,加密时用了 false,解密时也必须用 false,否则解出来的就是一堆乱码,或者直接抛出异常。
- 如何选择:PKCS#1 v1.5 是默认且兼容性最广的方案,但存在理论上的选择密文攻击风险;OAEP 则更安全,但要求 .NET Framework 4.6+ 或 .NET Core 2.1+ 的环境。
- 跨语言/工具互通:如果你用的是 OpenSSL 生成的密钥,或者需要与 Ja va 端交互,务必确认双方使用的是同一种填充模式。Ja va 中的
"RSA/ECB/PKCS1Padding"对应 C# 的false;而"RSA/ECB/OAEPWithSHA-1AndMGF1Padding"才对应 C# 的true。 - 千万别混淆:注意区分
Encrypt(加密原始数据)和SignData(对数据的哈希值进行签名),两者的输入和语义完全不同,不能混用。
Base64 编码/解码环节最容易漏掉字节对齐
加密后得到的是字节数组(byte[]),要转换成字符串传输,必须使用 Convert.ToBase64String;解密前,则必须用 Convert.FromBase64String 将其还原为字节数组。如果中间混入了 URL-safe Base64、截断了空格,或者错误地将 Base64 字符串用 UTF-8 编码成字节数组,整个流程就会彻底失败。
- 典型错误:
Encoding.UTF8.GetBytes("base64string")。这行代码是在对 Base64 字符串本身进行 UTF-8 编码,而不是在解码 Base64,结果自然是错的。 - 正确链路:原始字符串 → UTF8 字节数组 →
Encrypt→ 密文字节数组 →ToBase64String→ 传输;接收端 →FromBase64String→ 密文字节数组 →Decrypt→ UTF8 字节数组 →GetString。 - 一个隐藏陷阱:在 Windows 下,从记事本(Notepad)复制粘贴 Base64 字符串时,可能会被自动加上换行符,导致解码失败。一个简单的处理方法是:
string.Replace("\r\n", "")来清洗一下。
说到底,真正的挑战从来不是那几行 API 调用。麻烦往往藏在细节里:密钥怎么存储、由谁生成、跨平台如何验证、出错时该看哪条堆栈信息。RSA 不是一个可以随意对待的黑盒,它的每个参数背后,都对应着严格的密码学约束。跳过这些细节,即使代码在测试环境跑通了,也难保不会在某个特殊的字符集、某次新的部署,或者某个不同的 JDK 版本上突然“罢工”。
