惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
K
Kaspersky official blog
T
The Exploit Database - CXSecurity.com
NISL@THU
NISL@THU
AWS News Blog
AWS News Blog
V2EX - 技术
V2EX - 技术
Google DeepMind News
Google DeepMind News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Security @ Cisco Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
J
Java Code Geeks
Microsoft Azure Blog
Microsoft Azure Blog
Attack and Defense Labs
Attack and Defense Labs
Jina AI
Jina AI
The Last Watchdog
The Last Watchdog
W
WeLiveSecurity
H
Help Net Security
V
Visual Studio Blog
宝玉的分享
宝玉的分享
C
Cybersecurity and Infrastructure Security Agency CISA
T
Threat Research - Cisco Blogs
IT之家
IT之家
Hugging Face - Blog
Hugging Face - Blog
Latest news
Latest news
T
Tor Project blog
I
Intezer
美团技术团队
GbyAI
GbyAI
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 三生石上(FineUI控件)
Google DeepMind News
Google DeepMind News
Scott Helme
Scott Helme
Y
Y Combinator Blog
博客园 - 司徒正美
T
Tenable Blog
O
OpenAI News
N
News and Events Feed by Topic
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
Vulnerabilities – Threatpost
P
Palo Alto Networks Blog
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
云风的 BLOG
云风的 BLOG
Help Net Security
Help Net Security

土法炼钢兴趣小组的算法知识备份

国密算法与国密 TLS 系列索引 【系统架构设计】架构质量属性:不只是"高可用高性能" 【系统架构设计百科】告警策略:如何避免"狼来了" 【系统架构设计】CQRS:读写分离的架构哲学 【系统架构设计】空间架构:极端扩展场景的解法 【系统架构设计】微服务架构深度审视:优势、代价与适用边界 【系统架构设计】扩展性原理:水平、垂直与对角扩展 【系统架构设计】无状态设计:扩展的第一步也是最难的一步 【系统架构设计】缓存架构:从本地到分布式的多级缓存体系 【系统架构设计】管道与过滤器:Unix 哲学的架构表达 【系统架构设计】复杂性管理:架构的核心战场 【系统架构设计】消息队列架构:异步解耦的设计与陷阱 【系统架构设计】CDN 架构:全球加速的设计原理 【系统架构设计】连接池设计:被忽视的性能杀手 【系统架构设计】弹性设计模式:熔断器、舱壁与超时 【系统架构设计】高可用设计模式:冗余、故障转移与仲裁 【系统架构设计】容量规划:从拍脑袋到数据驱动 【系统架构设计】数据库扩展:分库分表的工程实践与替代方案 【系统架构设计】SLO 工程:可靠性的量化管理 【系统架构设计】性能建模:用数学思维分析系统瓶颈 【系统架构设计】混沌工程:主动验证系统的韧性 【系统架构设计】零拷贝与内存映射:数据搬运的极致优化 【系统架构设计】线程模型:从 thread-per-request 到协程 【系统架构设计】容灾架构:多活与灾备设计 【系统架构设计】数据库性能模式:索引、查询与连接管理 【系统架构设计】数据建模:从关系范式到文档模型的真实权衡 【系统架构设计】吞吐量优化:批处理、流水线与并发模型 【系统架构设计】流处理架构:从批处理到实时的范式迁移 【系统架构设计】搜索引擎架构:倒排索引之上的系统设计 【系统架构设计】时序数据架构:监控与 IoT 的存储设计 【系统架构设计】数据迁移与版本化:在线不停机的数据演进 【系统架构设计】数据湖与数据仓库:分析架构的演进路线 【系统架构设计】API 网关设计:入口层的职责边界 【系统架构设计】应用层数据一致性模式:在正确性与性能之间走钢丝 【系统架构设计】多模数据库选型:Polyglot Persistence 的工程实践 【系统架构设计】服务发现与注册:动态拓扑的基础设施 【系统架构设计】配置管理架构:从配置文件到配置中心 【系统架构设计】全链路压测:大规模系统的性能验证 【系统架构设计】幂等性设计:分布式环境下的安全重试 【系统架构设计】契约测试与 Schema 演进:服务间的信任协议 【系统架构设计】长连接与推送架构:WebSocket、SSE 与 MQTT 【系统架构设计】延迟分析:从 P50 到 P999 的全链路追踪 【系统架构设计百科】DDD 战术模式:聚合、实体与值对象 【系统架构设计百科】防腐层与开放主机服务:系统集成的 DDD 方案 【系统架构设计百科】领域事件与事件风暴:从业务到架构的桥梁 【系统架构设计百科】CQRS + Event Sourcing 完整实战:从领域建模到部署 【系统架构设计百科】DDD 与微服务:用领域模型划分服务边界 【系统架构设计】DDD 战略设计:限界上下文与上下文映射 【系统架构设计百科】认证架构:从 Session 到 JWT 到 OIDC 【系统架构设计】API 设计哲学:REST vs GraphQL vs gRPC 的真实权衡 排序算法专题:从 TimSort 到并行排序 【密码学百科】国密算法体系:SM2/SM3/SM4/SM9 全景解读 【密码学百科】承诺方案:Pedersen 承诺、向量承诺与多项式承诺 【密码学百科】不经意传输与隐私信息检索:OT、OT 扩展与 PIR 【密码学百科】门限密码学:门限签名、门限解密与分布式密钥生成 完美哈希:从理论到 gperf 实践 【密码学百科】安全多方计算:从 Yao 的混淆电路到实用 MPC 【密码学百科】同态加密:从 Paillier 到全同态加密(FHE) 【密码学百科】零知识证明系统:zk-SNARKs、zk-STARKs 与 Bulletproofs 【密码学百科】概率论与密码分析:生日攻击、差分分析与线性分析 【密码学百科】计算复杂性与归约:密码安全性证明的基石 【密码学百科】秘密共享:Shamir 方案、VSS 与安全多方计算入口 【密码学百科】椭圆曲线代数:Weierstrass 方程、点群运算与曲线选择 【密码学百科】离散对数与配对密码学:从 DLP 到 BLS 签名 【密码学百科】格密码数学基础:SVP、LWE 与格基约化 【密码学百科】抽象代数:群、环、域的密码学视角 【密码学百科】有限域算术:GF(2^n) 运算与在 AES/ECC 中的应用 【密码学百科】数论进阶:二次剩余、椭圆曲线上的 Weil 配对 【密码学百科】密码学简史:从凯撒密码到量子时代 【密码学百科】威胁模型与安全目标:CIA 三要素之外 【密码学百科】Kerckhoffs 原则与现代密码设计哲学 【密码学百科】随机性:密码学的基石 【密码学百科】信息论入门:熵、完美保密与 Shannon 定理 【密码学百科】分组密码原理:Feistel 网络与 SPN 结构 【密码学百科】AES 逐步拆解:SubBytes 到 MixColumns 的数学 【密码学百科】分组密码工作模式全览:ECB/CBC/CTR/OFB/CFB 【密码学百科】流密码:RC4 的兴衰与 ChaCha20 的崛起 【密码学百科】密码学哈希函数:MD5→SHA-2→SHA-3 的进化之路 【密码学百科】MAC 与 HMAC:消息认证的正确姿势 【密码学百科】认证加密(AEAD):GCM、ChaCha20-Poly1305 与 OCB 【密码学百科】密钥派生函数:HKDF、PBKDF2、Argon2 与密码存储 【密码学百科】公钥密码的数论基础:模运算、群、原根 【密码学百科】Diffie-Hellman 密钥交换与离散对数问题 【密码学百科】椭圆曲线密码学(ECC):从几何直觉到点群运算 【密码学百科】数字签名:ECDSA、EdDSA 与 Schnorr 签名 【密码学百科】现代密钥交换:X25519、ECDHE 与前向保密 【密码学百科】混合加密与 KEM/DEM 范式:ECIES 与 HPKE 【密码学百科】填充方案:PKCS#1 v1.5、OAEP 与 PSS 【密码学百科】TLS 协议全解析:从握手到 0-RTT 【密码学百科】PKI 与数字证书:信任链的构建与崩塌 【密码学百科】密码认证协议:从 SRP 到 OPAQUE 【密码学百科】零知识证明入门:如何证明你知道而不泄露 【密码学百科】安全信道构造:Noise 协议框架与 Signal 协议 【密码学百科】密钥管理工程:HSM、KMS 与密钥生命周期 【密码学百科】侧信道攻击:从时序攻击到功耗分析 【密码学百科】密码学实现陷阱:三层漏洞分类、审计工具链与系统性预防 密码敏捷性:如何设计可升级的密码系统 【密码学百科】OpenSSL/BoringSSL 架构剖析:ENGINE、Provider 与 FIPS 模块 【Linux 网络子系统深度拆解】内核网络调优方法论:从基准测试到生产验证 排序基准测试:用数据说话
【密码学百科】RSA 从原理到攻击:教科书 RSA 为什么不安全
2026-04-04 · via 土法炼钢兴趣小组的算法知识备份

1977 年,罗纳德·里维斯特(Ron Rivest)、阿迪·沙米尔(Adi Shamir)与伦纳德·阿德曼(Leonard Adleman)在麻省理工学院发表了一种新的公钥加密方案,以三人姓氏首字母命名为 RSA。这是人类第一个同时可用于加密与数字签名的公钥密码体制,也是至今工业界部署最广泛的非对称算法之一。然而,教科书中那段简洁优美的数学公式只描绘了 RSA 的骨架;真实世界里,从密钥生成的每一个比特到填充方案的每一个字节,都暗藏着精巧的攻击面。本文将从最基础的密钥生成出发,逐步展示教科书 RSA 为何不安全,以及工程实践如何在数十年的攻防博弈中一步步堵住漏洞。

RSA 算法流程:密钥生成、加密与解密

先用一张图把本文的核心关系串起来。

flowchart TD
    A[选 p q] --> B[计算 n 与 phi]
    B --> C[选择 e]
    C --> D[求私钥 d]
    D --> E[加密与签名]

RSA 的安全性建立在大整数分解问题(Integer Factorization Problem)之上:给定两个大素数 p 和 q 的乘积 n = p·q,在已知 n 的情况下恢复 p、q 在计算上是不可行的。密钥生成的每一步都直接决定了这一假设能否成立。

选取大素数 p 和 q。 现代标准要求 p 和 q 各自的位长为密钥长度的一半。若密钥长度为 2048 比特,则 p 和 q 各为 1024 比特的素数。生成过程通常是在密码学安全的随机数发生器(CSPRNG)中连续采样奇数,然后对每个候选数执行 Miller-Rabin 素性测试,重复足够多轮以将误判概率压至可忽略的水平。

工程上还会对 p 和 q 施加额外限制。首先,|p − q| 必须足够大,否则费马因式分解法(Fermat factorization)能在极短时间内分解 n。其次,p − 1 和 q − 1 不应含有过小的因子,否则 Pollard 的 p − 1 算法可以轻松分解 n。满足此条件的素数常被称为”安全素数”(safe prime),即 p 和 q 的形式为 p = 2p’ + 1,其中 p’ 同样是素数。虽然并非所有标准都要求使用安全素数,但 FIPS 186-5 和许多密码库将其作为推荐实践。

选取公开指数 e。 公开指数 e 须满足 1 < e < λ(n) 且 gcd(e, λ(n)) = 1,其中 λ(n) = lcm(p − 1, q − 1) 是卡迈克尔函数(Carmichael function)。历史上曾使用 e = 3 以加速加密运算,但过小的 e 会引发后文将详述的 Håstad 攻击等一系列问题。当今的事实标准是 e = 65537(即 2^16 + 1),这是一个费马素数,其二进制表示仅含两个 1,使模幂运算只需 17 次平方加一次乘法,兼顾了效率与安全。

计算私钥指数 d。 私钥 d 是 e 对 λ(n) 的模逆元,即 d·e ≡ 1 (mod λ(n))。此值通过扩展欧几里得算法(Extended Euclidean Algorithm)在多项式时间内计算。d 的长度通常与 n 相当,约为 2048 比特。若 d 过短,则 Wiener 攻击或 Boneh-Durfee 攻击可以在多项式时间内恢复 d,这将在第六节详细讨论。

密钥长度建议。 2024 年,NIST 建议 RSA 密钥长度不低于 2048 比特以达到 112 比特的安全等级,而 3072 比特可达 128 比特安全等级。要达到 256 比特安全(对应 AES-256 的等级),RSA 密钥需要超过 15000 比特,这在性能上几乎不可接受,也正是 RSA 逐渐被椭圆曲线密码(ECC)替代的重要原因之一。

二、教科书 RSA

理解了密钥生成之后,我们先给出最原始的、未经任何修饰的 RSA 方案——即所谓”教科书 RSA”(Textbook RSA)。

加密。 设公钥为 (n, e),明文消息 m 是一个满足 0 ≤ m < n 的整数。加密运算为:

c = m^e mod n

密文 c 同样是模 n 下的一个整数。

解密。 持有私钥 d 的接收者计算:

m = c^d mod n

正确性证明。 为什么解密能够恢复明文?根据欧拉定理(Euler’s theorem),若 gcd(m, n) = 1,则 m^φ(n) ≡ 1 (mod n)。因为 d·e ≡ 1 (mod λ(n)),存在整数 k 使得 d·e = 1 + k·λ(n)。于是:

c^d = (m^e)^d = m^(e·d) = m^(1 + k·λ(n)) = m · (m^λ(n))^k ≡ m · 1^k = m (mod n)

当 gcd(m, n) ≠ 1 时(即 m 恰好是 p 或 q 的倍数),可分别在模 p 和模 q 下验证等式成立,再由中国剩余定理(CRT)合并得到相同结论。需注意,此处使用 λ(n) = lcm(p−1, q−1) 比使用 φ(n) = (p−1)(q−1) 更为精确,因为 λ(n) 是满足 m^λ(n) ≡ 1 (mod n) 的最小正指数。

签名与验证。 教科书 RSA 的签名是加密的”镜像”操作:签名者用私钥 d 计算 σ = m^d mod n,验证者用公钥 e 检查 m ≡ σ^e (mod n)。

下面给出一个最小化的 Python 实现,仅用于演示原理,切勿将其用于任何生产环境:

import random
from math import gcd

def is_probable_prime(n, k=20):
    """Miller-Rabin 素性测试"""
    if n < 2:
        return False
    if n == 2 or n == 3:
        return True
    if n % 2 == 0:
        return False
    # 将 n-1 写成 2^r · d
    r, d = 0, n - 1
    while d % 2 == 0:
        r += 1
        d //= 2
    for _ in range(k):
        a = random.randrange(2, n - 1)
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False
    return True

def gen_prime(bits):
    """生成指定位长的素数"""
    while True:
        p = random.getrandbits(bits) | (1 << (bits - 1)) | 1
        if is_probable_prime(p):
            return p

def extended_gcd(a, b):
    if a == 0:
        return b, 0, 1
    g, x1, y1 = extended_gcd(b % a, a)
    return g, y1 - (b // a) * x1, x1

def mod_inverse(e, phi):
    g, x, _ = extended_gcd(e % phi, phi)
    if g != 1:
        raise ValueError("模逆元不存在")
    return x % phi

def keygen(bits=2048):
    """教科书 RSA 密钥生成"""
    e = 65537
    while True:
        p = gen_prime(bits // 2)
        q = gen_prime(bits // 2)
        if p == q:
            continue
        lam = (p - 1) * (q - 1) // gcd(p - 1, q - 1)  # lcm(p-1, q-1)
        if gcd(e, lam) == 1:
            break
    n = p * q
    d = mod_inverse(e, lam)
    return (n, e), (n, d)

def encrypt(pub, m):
    n, e = pub
    return pow(m, e, n)

def decrypt(priv, c):
    n, d = priv
    return pow(c, d, n)

# 演示
pub, priv = keygen(512)  # 仅为演示,512 位极不安全
message = 42
ciphertext = encrypt(pub, message)
plaintext = decrypt(priv, ciphertext)
assert plaintext == message
print(f"明文: {message}, 密文: {ciphertext}, 解密: {plaintext}")

这段代码在数学上完全正确。但正如我们接下来将看到的那样,它在密码学意义上是彻底不安全的。

三、教科书 RSA 的致命缺陷

教科书 RSA 之所以被称为”教科书版本”,正是因为它仅存在于教科书之中,任何严肃的密码学实现都不会直接使用它。它至少存在以下几类根本性缺陷。

确定性加密——不满足 IND-CPA 安全。 相同的明文 m 在相同的公钥下总是产生相同的密文 c。这意味着攻击者可以通过观察密文是否重复来获取明文的统计信息。在实际场景中,假设一家银行使用教科书 RSA 加密转账金额,攻击者只需预先加密所有常见金额(如整百整千的数值),然后将截获的密文逐一比对,即可在毫秒级时间内推断每笔交易的金额。更严格地说,教科书 RSA 无法通过选择明文攻击下的不可区分性(IND-CPA)测试:攻击者只需选择两个明文 m0 和 m1,在收到挑战密文后自行加密 m0,若结果与挑战密文一致则明文为 m0,否则为 m1。攻击者的优势为 1,系统彻底崩塌。

乘法同态性——可锻造性。 教科书 RSA 具有乘法同态性质:若 c1 = m1^e mod n,c2 = m2^e mod n,则 c1·c2 mod n = (m1·m2)^e mod n。这意味着攻击者无需知道任何明文,即可将两段密文”融合”为一段新密文,其对应的明文恰为原始两段明文的乘积。这种可锻造性(malleability)在拍卖、电子投票等场景中是灾难性的。

更具体地说,假设攻击者拦截了密文 c = m^e mod n,想获取明文 m 但无法直接向解密预言机提交 c。攻击者可以选取任意 s,计算 c’ = s^e · c mod n,将 c’ 提交给解密预言机。预言机返回 m’ = c’^d mod n = s·m mod n,攻击者再计算 m = m’·s^(−1) mod n 即可恢复原始明文。这就是经典的”选择密文攻击”(Chosen-Ciphertext Attack, CCA)。

缺乏语义安全。 除了上述两点之外,教科书 RSA 对小消息空间完全无力抵抗穷举搜索。例如,若明文是一个 32 比特的社会保障号码,攻击者只需对 2^32 个可能值逐一加密并与密文比对。由于加密是确定性的,匹配成功即意味着解密成功。

四、Håstad 广播攻击

Håstad 广播攻击(Håstad’s broadcast attack)是最早被发现的针对低公开指数 RSA 的攻击之一。它的场景简单而实际:同一明文 m 被发送给多个接收者,每个接收者使用不同的模数 n_i 但相同的公开指数 e。

假设 e = 3,攻击者截获三段密文:

c1 = m^3 mod n1
c2 = m^3 mod n2
c3 = m^3 mod n3

由于 n1、n2、n3 两两互素(否则可直接通过 gcd 分解模数),中国剩余定理告诉我们可以唯一确定一个整数 C,使得:

C ≡ c1 (mod n1)
C ≡ c2 (mod n2)
C ≡ c3 (mod n3)
且 0 ≤ C < n1·n2·n3

因为 m < n_i 对每个 i 成立,所以 m^3 < n1·n2·n3。这意味着 C = m^3 在整数意义上成立(不涉及任何取模),攻击者只需对 C 计算整数立方根即可恢复 m。

更一般地,当公开指数为 e 时,攻击者需要截获 e 段密文即可发起攻击。这就是为什么现代实践中推荐 e = 65537 而非 e = 3 的重要原因——即使攻击者收集到了 65537 段密文,中国剩余定理重建后的整数也不等于 m^65537 在整数上的值(因为 m^65537 极为巨大)。当然,真正的防御手段是使用合适的填充方案,而非仅仅依赖较大的 e。

以下是 Håstad 攻击的 Python 演示:

from functools import reduce

def integer_cube_root(n):
    """牛顿法计算整数立方根"""
    if n < 0:
        return -integer_cube_root(-n)
    if n == 0:
        return 0
    x = 1 << ((n.bit_length() + 2) // 3)
    while True:
        y = (2 * x + n // (x * x)) // 3
        if y >= x:
            return x
        x = y

def crt(remainders, moduli):
    """中国剩余定理"""
    N = reduce(lambda a, b: a * b, moduli)
    result = 0
    for r_i, n_i in zip(remainders, moduli):
        N_i = N // n_i
        _, x, _ = extended_gcd(N_i % n_i, n_i)
        result += r_i * N_i * x
    return result % N

def hastad_attack(ciphertexts, moduli):
    """Håstad 广播攻击 (e=3)"""
    C = crt(ciphertexts, moduli)
    m = integer_cube_root(C)
    return m

# 演示:三个接收者,e=3
e = 3
keys = []
for _ in range(3):
    while True:
        p, q = gen_prime(256), gen_prime(256)
        lam = (p - 1) * (q - 1) // gcd(p - 1, q - 1)
        if gcd(e, lam) == 1:
            break
    n = p * q
    d = mod_inverse(e, lam)
    keys.append((n, e, d))

secret_message = 123456789
ciphertexts = [pow(secret_message, e, n_i) for n_i, _, _ in keys]
moduli = [n_i for n_i, _, _ in keys]

recovered = hastad_attack(ciphertexts, moduli)
assert recovered == secret_message
print(f"Håstad 攻击成功!恢复明文: {recovered}")

五、Bleichenbacher 攻击

1998 年,丹尼尔·布莱兴巴赫(Daniel Bleichenbacher)在 CRYPTO 会议上发表了一项震动密码学界的攻击。他证明了 PKCS#1 v1.5 填充方案存在填充预言机漏洞(padding oracle),攻击者可以通过自适应选择密文攻击(adaptive chosen-ciphertext attack)在大约一百万次查询内完全恢复明文。

PKCS#1 v1.5 填充格式。 在加密之前,明文 m 被编码为如下格式:

0x00 || 0x02 || [随机非零填充字节] || 0x00 || [明文数据]

其中随机填充字节至少 8 个,总长度等于密钥字节数(如 256 字节对应 2048 位密钥)。接收者在解密后检查最高两字节是否为 0x00 和 0x02,以及后续是否存在分隔符 0x00。

攻击原理。 Bleichenbacher 发现,许多 TLS 服务器在收到格式不正确的密文时会返回不同的错误消息——一种是”填充格式错误”,另一种是”解密后数据不合法”。这种差异构成了一个一比特的填充预言机。攻击者利用 RSA 的乘法同态性质,构造形如 c’ = (s^e · c) mod n 的变体密文并发送给服务器。如果服务器接受了填充格式(返回非填充错误),攻击者就知道 s·m mod n 的最高字节为 0x00 0x02,从而将 m 的可能范围缩小。通过二分搜索式的迭代,攻击者在大约 2^20(约一百万)次查询后即可精确恢复 m。

历史影响。 这一攻击直接促使 PKCS#1 v2.0 引入了 OAEP 填充方案。然而,由于向后兼容的压力,PKCS#1 v1.5 在 TLS 中存活了近二十年。2017 年,Hanno Böck、Juraj Somorovsky 和 Craig Young 发表了 ROBOT(Return Of Bleichenbacher’s Oracle Threat)攻击,证明包括 Facebook、PayPal 在内的大量知名网站仍然容易受到 Bleichenbacher 类攻击。他们在现代 TLS 栈中发现了各种微妙的侧信道——响应时间差异、错误码差异、甚至 TCP 连接重置行为的差异——都足以充当填充预言机。

这一事件深刻地告诉我们:密码学协议中的错误处理必须做到严格的常量时间(constant-time),且不能泄露任何关于解密结果的信息。TLS 1.3 最终彻底移除了 RSA 密钥传输机制,从协议层面根除了这一攻击面。

六、Coppersmith 方法

1996 年,唐·科波斯密斯(Don Coppersmith)基于格基归约(lattice basis reduction)技术提出了一种强大的方法,可以在多项式时间内找到模多项式的小根。这一方法在 RSA 分析中催生了一系列极具威力的攻击。

小消息攻击。 若明文 m 相对于模数 n 较小(例如 m < n^(1/e)),教科书 RSA 的密文 c = m^e 在整数上就已经等于 m^e(无需取模),直接开 e 次方根即可恢复明文。这实际上是 Håstad 攻击的一种退化情形。Coppersmith 方法更进一步:即使 m 稍大于 n^(1/e),只要满足 m < n^(1/e) 的某个宽松上界,攻击者仍可通过构造关于 m 的格(lattice)并执行 LLL 算法来恢复 m。

部分密钥泄露攻击。 若攻击者通过侧信道获得了私钥 d 的部分比特(例如低位的一半),Coppersmith 方法可以在多项式时间内恢复完整的 d。具体而言,Boneh、Durfee 和 Frankel 在 1998 年证明,知道 d 的低 n/4 位即足以在多项式时间内分解 n。这对硬件实现中的侧信道防护提出了极高要求。

Boneh-Durfee 攻击(小私钥攻击)。 1999 年,Dan Boneh 和 Glenn Durfee 证明,当 d < n^0.292 时,可以在多项式时间内恢复 d。这改进了 Michael Wiener 在 1990 年提出的 d < n^0.25 的界限(Wiener 攻击基于连分数逼近)。Boneh-Durfee 攻击的核心思想是将恢复 d 的问题转化为在某个格上寻找短向量的问题,然后利用 LLL 或 BKZ 算法求解。

Franklin-Reiter 相关消息攻击。 若两段明文之间存在已知的线性关系 m2 = a·m1 + b(其中 a、b 已知),攻击者在截获两段对应密文后,可以利用多项式公因子分解在多项式时间内恢复两段明文。Coppersmith 方法将此推广到更一般的多项式关系。

这些攻击共同说明了一个主题:RSA 的安全性不仅依赖于大整数分解的困难性,还极度依赖于使用方式的正确性。密钥和消息中任何结构性的”短”特征,都可能被格方法精确捕获并利用。

七、RSA-OAEP 与 RSA-PSS

面对教科书 RSA 的种种缺陷,密码学家设计了两套标准化的填充方案:用于加密的 RSA-OAEP 和用于签名的 RSA-PSS。

RSA-OAEP(Optimal Asymmetric Encryption Padding)。 OAEP 由 Mihir Bellare 和 Phillip Rogaway 在 1994 年提出,后经 Fujisaki、Okamoto、Pointcheval 和 Stern 完善安全证明。其核心结构是一个双轮费斯妥网络(two-round Feistel network):

  1. 生成一个随机种子 r。
  2. 计算 maskedDB = DB ⊕ G(r),其中 DB 是填充后的明文数据块,G 是一个掩码生成函数(Mask Generation Function, MGF),通常基于 SHA-256 实现。
  3. 计算 maskedSeed = r ⊕ H(maskedDB),其中 H 是另一个哈希函数。
  4. 将 0x00 || maskedSeed || maskedDB 作为编码后的消息,执行 RSA 加密。

这种结构引入了随机性(解决确定性问题),将明文均匀分散到整个模数空间(防止小消息攻击),并且使得任何对密文的修改都会以压倒性的概率导致解密失败(抵抗可锻造性)。在随机预言机模型(Random Oracle Model, ROM)下,RSA-OAEP 被证明满足 IND-CCA2 安全——这是公钥加密最强的标准安全定义。

PKCS#1 v2.2 将 RSA-OAEP 定义为推荐的加密方案,NIST SP 800-56B 也将其列为密钥传输的批准方法。

RSA-PSS(Probabilistic Signature Scheme)。 PSS 同样由 Bellare 和 Rogaway 提出,是一种概率签名方案。与传统的”哈希再签名”(hash-then-sign)不同,PSS 在签名过程中引入随机盐值(salt),使得同一消息的每次签名都不相同。其结构同样基于掩码生成函数:

  1. 计算消息的哈希值 mHash = Hash(M)。
  2. 生成随机盐值 salt。
  3. 计算 H’ = Hash(padding || mHash || salt)。
  4. 构造编码消息 EM,其中包含 maskedDB(由 salt 位置信息经 MGF(H’) 掩码后得到)和 H’。
  5. 对 EM 执行 RSA 签名运算。

在 ROM 下,RSA-PSS 被证明是紧致安全的(tightly secure):其安全性可直接归约到 RSA 问题本身,归约损失极小。这意味着 RSA-PSS 几乎能”完全利用”RSA 问题的困难性。FIPS 186-5 已将 RSA-PSS 列为唯一推荐的 RSA 签名方案,PKCS#1 v1.5 签名方案虽仍被允许用于遗留系统验证,但不再推荐用于新系统。

八、CRT 优化与故障攻击

CRT 加速。 直接计算 m = c^d mod n 需要对一个约 2048 位的指数执行模幂运算,开销巨大。利用中国剩余定理(CRT)可以将这一运算拆分为两个较小的模幂运算:

m_p = c^(d mod (p-1)) mod p
m_q = c^(d mod (q-1)) mod q

然后通过 CRT 合并:

m = CRT(m_p, m_q) = m_q + q · ((m_p - m_q) · q^(-1) mod p)

由于每个子运算的模数和指数都只有原来的一半大小,而模幂的计算量大致与指数长度的立方成正比,CRT 优化可以带来约四倍的加速。这就是为什么几乎所有 RSA 实现——从 OpenSSL 到硬件安全模块(HSM)——都使用 CRT 形式的私钥操作。

Bellcore 故障攻击。 1997 年,Boneh、DeMillo 和 Lipton 发现了 CRT-RSA 实现的一个惊人弱点:如果在计算 m_p 或 m_q 的过程中发生单比特计算错误(例如由宇宙射线、电压毛刺或激光注入引起),攻击者可以通过一次正确签名和一次错误签名直接分解 n。

具体地,假设 m_q 的计算正确而 m_p 的计算出错,得到错误的 m̃_p。则错误签名 σ̃ 满足:

σ̃^e ≡ m (mod q)  但  σ̃^e ≢ m (mod p)

因此 gcd(σ̃^e − m, n) = q,攻击者立即获得 q,进而分解 n。

对策。 防御故障攻击的标准做法是在输出签名之前进行验证:计算 σ^e mod n 并检查是否等于原始消息的编码。若不等则丢弃签名并重新计算。这一额外的模幂运算仅增加约百分之几的开销(因为 e = 65537 很小,验证很快),却能完全阻止故障攻击。此外,一些实现还会使用随机化技术(如 RSA 盲化,RSA blinding)来抵抗时序分析和功耗分析等侧信道攻击:在计算前将密文乘以一个随机因子 r^e,计算完成后再除以 r,使得每次运算的中间值都是随机的。

RSA 攻击谱系总览

回顾前文讨论的各类攻击,我们可以将 RSA 的攻击面整理为从数学层到协议层的完整谱系:

RSA 攻击谱系
│
├── 数学层攻击(Mathematical Attacks)
│   ├── 通用整数分解
│   │   ├── 数域筛法(GNFS) ─────────── 所有 RSA 变体(密钥长度不足时)
│   │   └── 量子分解(Shor 算法) ────── 所有 RSA 变体(量子计算机可用时)
│   ├── Coppersmith 方法 ─────────────── 低公开指数 + 部分信息泄露场景
│   │   └── 已知明文高位/低位时恢复完整明文
│   ├── Wiener 攻击 ──────────────────── d < n^(1/4)/3 的小私钥指数 RSA
│   └── Håstad 广播攻击 ──────────────── 教科书 RSA + 小 e + 多接收者广播
│
├── 实现层攻击(Implementation Attacks)
│   ├── 时序攻击(Timing Attack) ────── 非常量时间的模幂运算实现
│   ├── 功耗/电磁分析 ────────────────── 无掩码保护的硬件/嵌入式实现
│   ├── 故障注入攻击(Fault Attack) ── CRT-RSA 签名(无签名后验证时)
│   └── 缓存侧信道 ──────────────────── 使用查表的滑动窗口模幂实现
│
└── 协议层攻击(Protocol-Level Attacks)
    ├── Bleichenbacher 攻击 ──────────── PKCS#1 v1.5 填充的 RSA 加密
    │   └── ROBOT 攻击(2017) ────────── TLS 服务器中残存的 Bleichenbacher 预言机
    ├── 填充预言攻击 ─────────────────── 错误消息区分填充失败与解密失败的实现
    ├── 选择密文攻击(CCA) ──────────── 教科书 RSA(乘法同态性)
    └── Manger 攻击 ─────────────────── RSA-OAEP 的不当实现(泄露解码步骤信息)
攻击类别 代表性攻击 目标变体 核心利用点 主要对策
数学——分解 GNFS、Shor 所有 RSA 模数 n 的因子分解 增大密钥长度;迁移至后量子方案
数学——格方法 Coppersmith 低 e + 部分泄露 小根多项式求解 使用完整填充方案(OAEP)
数学——连分数 Wiener 小 d RSA 私钥指数过小 确保 d 足够大(标准密钥生成)
数学——CRT Håstad 广播 教科书 RSA, e=3 中国剩余定理还原 随机化填充(OAEP)
实现——时序 Kocher 时序攻击 非恒定时间实现 模幂运算时间差异 常量时间实现 + RSA 盲化
实现——故障 Bellcore 攻击 CRT-RSA 签名 单故障暴露因子 签名后验证;冗余计算
协议——填充 Bleichenbacher PKCS#1 v1.5 加密 填充有效性预言机 迁移至 RSA-OAEP;消除预言机
协议——残留 ROBOT TLS + RSA 密钥传输 服务器残存的错误区分 TLS 1.3 移除 RSA 密钥传输

梳理这张攻击谱系时,我常常感叹:RSA 的”脆弱”恰恰是密码学教育最宝贵的资产。没有任何一个单一原语像 RSA 这样,在数学层、实现层、协议层三个截然不同的维度上都拥有如此丰富的攻击案例。学习 Coppersmith 方法让你理解格约化;学习 Bleichenbacher 攻击让你理解填充方案为什么不能泄露一比特信息;学习 CRT 故障攻击让你理解为什么数学正确不等于实现安全。如果说现代密码分析是一门手艺,那么 RSA 就是每个学徒磨练技艺的第一块试金石——它教给你的不只是如何攻破一个方案,而是如何用攻击者的眼光审视整个系统。

从另一个视角看,RSA 堪称理解”数学优美性为何在密码学中可能是一把双刃剑”的罗塞塔石碑。RSA 之所以拥有如此丰富的攻击面,恰恰因为它的代数结构太”好”了——乘法同态性让选择密文攻击成为可能,公钥指数的数论性质催生了 Håstad 攻击和 Wiener 攻击,而模运算的平滑结构则为 Coppersmith 方法提供了格约化的切入点。对比之下,椭圆曲线上的离散对数问题几乎不存在类似的结构化攻击——不是因为 ECC 被研究得更少,而是因为椭圆曲线群缺乏整数环那种丰富的代数”把手”供攻击者抓取。这给密码学设计者一个深刻的教训:数学上越优美、结构越丰富的对象,往往给攻击者提供的工具也越多。现代密码学越来越倾向于选择”结构贫乏”的数学对象作为安全基础——从椭圆曲线到格,再到同源,每一代新原语的共同趋势是:刻意远离那些人类直觉上觉得”自然”的代数结构。

九、RSA 的未来

RSA 已经服役近半个世纪,其地位正在发生根本性的变化。

密钥长度的军备竞赛。 1999 年,512 位 RSA 被公开分解。2010 年,768 位 RSA 被分解(使用数域筛法,耗时约两年的集群计算时间)。2020 年,研究人员使用约 2700 个核心年的计算量分解了 829 位的 RSA 挑战数。目前普遍认为 1024 位 RSA 对国家级行为者已不安全,情报机构可能早已具备分解能力。2048 位在经典计算能力下预计至少安全到 2030 年代,但这一预测会随着算法改进和硬件发展而不断修正。值得注意的是,数域筛法的渐近复杂度为亚指数级,这意味着每增加一倍密钥长度,所需的破解时间远不止翻倍,但也远不如指数级增长那样令人安心。每次密钥长度的提升都伴随着显著的性能代价——RSA-4096 的签名速度约为 RSA-2048 的八分之一,而密钥生成时间可能长达数秒。

与椭圆曲线密码的性能对比。 在相同安全等级下,ECC 的密钥远短于 RSA:128 位安全对应 256 位 ECC 密钥,却需要 3072 位 RSA 密钥。这意味着 ECC 在带宽、存储和计算速度上全面优于 RSA。ECDSA 和 EdDSA 签名的生成和验证速度通常比同等安全级别的 RSA-PSS 快数倍至数十倍。在移动设备和物联网等资源受限的场景下,这种差距尤为显著。TLS 1.3 虽然仍支持 RSA 签名用于证书验证,但密钥协商已完全转向基于椭圆曲线的 ECDHE。

量子威胁。 1994 年,彼得·肖尔(Peter Shor)提出了一种量子算法,可以在量子计算机上以多项式时间分解大整数。这意味着一旦足够大规模的容错量子计算机(fault-tolerant quantum computer)问世,RSA 将被彻底击溃,无论密钥长度多大。当前的估计是,分解 2048 位 RSA 需要约 4000 个逻辑量子比特(对应数百万个物理量子比特),这超出了现有量子硬件的能力,但量子技术的进步速度可能比预期更快。

后量子迁移。 NIST 已于 2024 年正式发布了后量子密码标准(PQC),包括基于格的 ML-KEM(前称 CRYSTALS-Kyber)用于密钥封装和基于格的 ML-DSA(前称 CRYSTALS-Dilithium)用于数字签名。这些方案旨在抵抗量子计算攻击,同时在经典计算机上保持可接受的性能。各大科技公司和标准化组织正在推进从 RSA 和 ECC 向后量子密码的迁移计划。这一迁移并非一朝一夕之事——全球数十亿张数字证书、数以百万计的嵌入式设备和遗留系统都需要逐步更新。在过渡期间,混合方案(hybrid scheme)——同时使用传统算法和后量子算法——被广泛推荐以确保向前兼容。即便量子计算机尚未到来,“先存储后解密”(harvest now, decrypt later)的威胁已迫使各国政府将后量子迁移提上日程。

一个经常被忽视的观点是,RSA 的持续存在与其说是技术惯性,不如说是组织行为学的案例研究。2025 年的今天,仍然有大量系统在生成 4096 位的 RSA 密钥——尽管 256 位的 ECC 密钥在相同安全等级下体积只有前者的十六分之一、运算速度快一个数量级、带宽开销微乎其微。这种现象无法用技术原因解释,只能从组织和生态的角度理解:RSA 嵌入了无数的合规文件、采购规范、硬件安全模块的固件、以及工程师的肌肉记忆中。迁移密码算法的成本不在于写新代码,而在于修改每一份引用了旧算法的文档、通过每一项引用了旧算法的合规审计、以及说服每一位”RSA 用了四十年都没问题”的决策者。这是密码学领域最大的非技术挑战:密码学的进化速度远快于组织制度的进化速度。每一次密码学迁移——从 DES 到 AES、从 SHA-1 到 SHA-2、从 RSA 到 ECC、再到即将到来的后量子迁移——本质上都是同一个社会学问题的重演:技术社区早已达成共识,而部署惯性却需要十年甚至更久才能克服。

RSA 的故事是密码学演化的缩影:一个在理论上优美、在实践中复杂的方案,在攻防博弈中不断演化,最终在新的计算范式面前走向落幕。但它所带来的公钥密码学革命——密钥交换无需预共享秘密、数字签名可以被公开验证——这些根本性的思想突破将永远镌刻在信息安全的历史中。从最初教科书般简洁的数学公式,到填充方案的精心设计,再到侧信道防护的工程细节,RSA 的发展历程展现了密码学如何在纯数学、协议设计和系统工程三个层面上同时展开斗争。理解 RSA 的每一个细节,不仅是理解一个算法,更是理解密码学这门科学如何在理想与现实之间寻求平衡。


密码学百科系列 · 第 15 篇

← 上一篇:数论基础 | 系列目录 | 下一篇:Diffie-Hellman

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。