危险API警示录:RustCrypto RSA库hazmat裸RSA接口的使用边界与安全红线
【免费下载链接】RSARSA implementation in pure Rust项目地址: https://gitcode.com/gh_mirrors/rsa1/RSA
在 Rust 加密生态中,RustCrypto RSA 库(rsacrate)是最成熟的纯 Rust RSA 实现。但它的hazmat模块暴露了一个被称为"危险 API"(HAZARDOUS API)的裸 RSA 接口——它直接执行模幂运算,不添加也不校验任何填充(Padding)。绝大多数开发者都应该远离它。本文用一份完整指南,讲清楚 RustCrypto RSA 库 hazmat 接口能做什么、绝对不能做什么,以及踩线后会付出怎样的安全代价。
☢️ 什么是 hazmat:一个自带辐射符号的模块
打开 src/hazmat.rs 的第一行文档,你看到的不是一句代码注释,而是警告:
"This module holds functions that apply RSA's core encryption and decryption primitives to raw datawithout adding or removing appropriate padding. A well-reviewed padding scheme is crucial to the security of RSA, so there are very few valid use cases for this API."
"HAZMAT" 是危险品运输的缩写,密码学社区用它标记"会烧到使用者自己手"的接口。RustCrypto 团队甚至把它藏在hazmat编译特性(feature flag)后面(见 Cargo.toml 中的hazmat = []),你必须显式开启才能看到这三个函数:
| 函数 | 作用 | 隐藏的风险 |
|---|---|---|
rsa_encrypt | 用公钥做裸 RSA 加密(模幂) | 无随机性,相同明文得到相同密文 |
rsa_decrypt | 用私钥做裸 RSA 解密,可选加随机盲化 | 不校验填充,输出是原始整数 |
rsa_decrypt_and_check | 解密后回算m^e验证 CRT 计算正确性 | 仅防内部计算错误,不防攻击者 |
它们最终都指向核心实现 src/algorithms/rsa.rs——一个几十行就能看懂的m^e mod n。正是这种"简单",让它成为安全事故的重灾区。
⚠️ 为什么裸 RSA 绝对不能直接加密消息
理解了下面的三条安全红线,你就不需要再去"试试"裸 RSA 了。
红线一:无随机性 = 可区分明文
rsa_encrypt是确定性运算。同样的明文 + 同样的公钥 = 永远相同的密文。攻击者只需构建一张"候选明文 → 密文"字典,就能逐条比对、识别你的消息内容(比如密文总是那几个值之一,多半是 "yes"/"no")。而带填充的方案(如 OAEP)每次加密都注入随机数,同一明文每次密文都不同。
红线二:乘性同态 = 密文可被篡改
裸 RSA 满足E(a) · E(b) ≡ E(a·b) (mod n)。攻击者无需知道明文,就能把自己的密文乘以"目标明文的密文",让服务器解出一个被精确操纵过的结果。这在密码学上叫乘法同态性(multiplicativity),是裸 RSA 与生俱来的马其诺防线。OAEP 与 PKCS#1 v1.5 填充都通过结构校验把这条路堵死,裸 RSA 则完全敞开。
红线三:解密即泄密(Padding Oracle 的温床)
如果你拿rsa_decrypt的输出自己手写"检查填充是否合法",检查分支的耗时差异、错误信息的有无,都可能泄露私钥信息。src/algorithms/pkcs1v15.rs 中有一段值得所有开发者抄进笔记本的注释:
"Note that whether this function returns an error or not discloses secret information. If an attacker can cause this function to run repeatedly and learn whether each instance returned an error then they candecrypt and forge signatures as if they had the private key."
这正是历史上 Bleichenbacher 攻击(Bleichenbacher's attack)的原理:利用填充校验报错的时间差,几次交互就能还原私钥。库内部的填充校验特意写成了恒定时间(constant time)风格,而你一旦绕开高层 API 直接用 hazmat,这层保护就不存在了。
✅ hazmat 的合法使用场景:几乎没有,但有一个
hazmat 模块文档里说得很直白:它只应该用于实现已经过同行评审的高层密码构造。例如:
- 实现一个新的 RSA 填充/签名方案,且该方案的数学证明已由密码学家审查;
- 密码学研究、教学演示、测试框架内部的原语层封装;
- 互操作协议中必须复用同一模幂原语的场景(如密钥封装的内部步骤)。
反过来说,只要你满足以下任意一条,就不要碰hazmat:
- 你想"加密一段数据"——请改用 OAEP 或 PKCS#1 v1.5 加密;
- 你想"对一段数据签名"——请改用 PSS 或 PKCS#1 v1.5 签名;
- 你想"发明一种新的填充方式"——请先让密码学家评审,再考虑用原语层。
🛡️ 正确的安全姿势:改用带填充的高层 API
RustCrypto RSA 库真正推荐你用的,是这些内置填充方案(均基于 RFC 8017,模块组织见 src/algorithms.rs):
| 方案 | 用途 | 推荐度 |
|---|---|---|
| OAEP(src/algorithms/oaep.rs) | RSA 加密 | ⭐⭐⭐ 新系统的默认选择 |
| PSS(src/algorithms/pss.rs) | RSA 签名 | ⭐⭐⭐ 新系统的默认选择 |
| PKCS#1 v1.5(src/algorithms/pkcs1v15.rs) | 加密/签名(遗留兼容) | ⭐⭐ 仅兼容旧系统 |
典型的安全调用路径(伪代码级描述,完整示例见 README.md 与 src/lib.rs 的文档示例):
RsaPublicKey + Oaep<Sha256> → encrypt(消息) RsaPrivateKey + Oaep<Sha256> → decrypt(密文)高层 API 帮你做了三件 hazmat 永远不会做的事:注入/校验填充结构、恒定时间处理错误路径、可选地加随机盲化(hazmat 的rsa_decrypt传入Rng时会自动做盲化以掩盖时序特征,这层防护值得记住,但别指望它能抵消上面三条红线)。
📋 上手指令清单(Checklist)
- 你的 Cargo.toml 没有开启
hazmatfeature → 保持现状,这是最安全的状态 - 代码搜索
rsa_encrypt/rsa_decrypt,若出现于业务代码,全部替换为 OAEP/PSS 高层接口 - 若确需使用 hazmat:确认你实现的是已获同行评审的构造,并有密码学专家评审你的调用方式
- 解密路径的错误处理使用库提供的错误类型(见 src/errors.rs),不要自行比对填充字节
- 关注 SECURITY.md 与 README 中的安全公告——该库曾因 Marvin Attack(RUSTSEC-2023-0071)收到过安全告警,说明"连库本身都可能中枪",裸用原语的风险只会更高
总结
RustCrypto RSA 库的 hazmat 模块把"裸 RSA"做成了一管没有安全阀的高压水管:rsa_encrypt、rsa_decrypt、rsa_decrypt_and_check三个函数(src/hazmat.rs)在数学上完全正确,但在安全上是负资产——确定性、同态性、填充 oracle 三条红线条条致命。记住一句话就够了:填充方案才是 RSA 安全的全部,模幂运算只是搬运工。除非你在实现经过同行评审的密码构造,否则请始终使用 OAEP 加密与 PSS 签名,让hazmatfeature 永远停留在关闭状态。
【免费下载链接】RSARSA implementation in pure Rust项目地址: https://gitcode.com/gh_mirrors/rsa1/RSA
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考