当RSA的“小钥匙”遇上大模数:低加密指数攻击实战剖析
1. 为什么RSA的小钥匙会出问题我第一次在CTF比赛中遇到低加密指数攻击的场景时整个人都是懵的。题目给了一个RSA加密的密文公钥指数e3模数n特别大。按照常规思路这种参数看起来没什么问题但实际测试后发现竟然可以直接解密后来才知道这就是典型的低加密指数攻击场景。RSA加密的核心公式是c ≡ m^e mod n。当e取值过小时比如常见的e3或e5会出现两种情况要么m^e n此时模运算根本没起作用要么m^e比n大但不够大可以通过简单的数学技巧绕过。这就像用一把特别小的钥匙去开一个巨大的保险箱钥匙太小反而让开锁变得异常简单。在真实的安全评估中我遇到过不少开发者为了提升加密速度刻意选择小指数e的情况。他们认为大模数n已经足够安全却不知道这样反而会引入致命漏洞。下面我们就来深入分析这两种情况的攻击原理。2. 当m^e小于n时的直接开方攻击2.1 数学原理剖析这种情况最简单直接。当m^e n时模运算实际上没有起到任何作用因为c m^e mod n m^e。攻击者只需要对密文c开e次方就能直接得到明文m。举个例子假设我们加密的明文m10e3n10000。那么c 10^3 1000。攻击者看到c1000直接计算1000的立方根就能得到10。这个攻击简单到令人难以置信但确实存在于很多实际系统中。2.2 实战Python脚本解析from gmpy2 import iroot from Crypto.Util.number import long_to_bytes n 0x52d483c27... # 省略的长模数 e 0x3 c 0x10652cdfa... # 省略的密文 m iroot(c, e) if m[1]: # 检查是否完全开方 print(long_to_bytes(m[0]))这个脚本的关键在于gmpy2库的iroot函数它能高效计算大整数的整数根。我在实际使用中发现对于2048位的大模数如果明文长度较短这种攻击几乎是瞬间完成的。曾经在一次渗透测试中我用这个方法5分钟内就破解了一个电商网站的加密令牌。3. 当m^e大于n时的k值爆破攻击3.1 数学原理进阶当m^e n时情况稍微复杂些。此时c m^e mod n意味着m^e kn c其中k是某个正整数。如果我们能找出正确的k值就能通过计算(kn c)的e次根来恢复明文。关键在于k的取值范围其实不大。因为m是明文通常不会太大比如ASCII文本所以k值也不会太大。通过枚举k值我们就能找到满足条件的解。3.2 爆破攻击脚本实现from gmpy2 import iroot from Crypto.Util.number import long_to_bytes n 0xabc123... # 替换为实际模数 e 3 c 0xdef456... # 替换为实际密文 i 0 while True: candidate i * n c m iroot(candidate, e) if m[1]: # 找到整数根 print(long_to_bytes(m[0])) break i 1 if i 100000: # 设置合理上限防止无限循环 print(未找到有效解) break在实际CTF比赛中我发现k值通常不会超过100万。有一次遇到一个题目k值竟然只有5脚本运行不到1秒就解出了flag。这个攻击的成功率取决于明文的长度 - 明文越短k值越小攻击就越容易成功。4. 防御措施与最佳实践4.1 如何选择安全的公钥指数经过多次实战教训我现在推荐使用e65537(2^161)作为公钥指数。这个值足够大能有效防止低加密指数攻击同时计算效率也不错。它还有两个优点二进制表示中只有两个1使得快速幂运算更高效而且它是一个素数与φ(n)互质的概率很高。4.2 必要的填充方案单纯增大e还不够必须配合使用OAEP等填充方案。我曾在审计一个区块链项目时发现他们虽然用了e65537但因为没做填充还是存在部分明文可以被恢复的风险。正确的做法是from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA key RSA.generate(2048) cipher PKCS1_OAEP.new(key) ciphertext cipher.encrypt(b秘密消息)4.3 自动化检测方法在安全审计中我通常会编写自动化脚本检查RSA参数。以下是一个简单的检测示例def check_rsa_params(n, e): if e 65537: print(f警告公钥指数{e}过小建议使用65537) if n.bit_length() 2048: print(f警告模数长度{n.bit_length()}位建议至少2048位) # 检查是否容易分解 # 可以添加更多检查项...5. CTF实战案例深度解析去年在某次CTF比赛中遇到一道很有意思的题目。题目给出了三个不同的密文都是同一个明文用相同的e3和不同的n加密得到的。这种情况可以使用中国剩余定理进行攻击完全不需要考虑m^e和n的大小关系。解题脚本如下from gmpy2 import iroot from Crypto.Util.number import long_to_bytes from functools import reduce def chinese_remainder(n, a): sum 0 prod reduce(lambda a, b: a*b, n) for n_i, a_i in zip(n, a): p prod // n_i sum a_i * pow(p, -1, n_i) * p return sum % prod n_list [n1, n2, n3] # 三个模数 c_list [c1, c2, c3] # 三个密文 m_cubed chinese_remainder(n_list, c_list) m iroot(m_cubed, 3) if m[1]: print(long_to_bytes(m[0]))这个案例告诉我们即使单个密文看起来安全多个相关密文组合也可能导致漏洞。在实际开发中绝对不要用相同的明文和e值但不同的n值进行多次加密。