背景
e 选得太小(e=3,5,7),且明文 m 也很小(me<n),导致密文 c = me 根本没有"折返"到模 n 的范围内 ,此时 c 就是 me 本身,直接开 e 次方根就能恢复明文!
RSA 加密公式:
c = m e m o d n c = m^e \bmod n c=memodn
当 m e < n m^e < n me<n 时,模运算根本没起作用,密文 c 就是 m e m^e me 的精确值。
此时:
m = c e = c 1 / e m = \sqrte{c} = c^{1/e} m=ec =c1/e
这就是低加密指数攻击(Low Encryption Exponent Attack)。
什么是低加密指数攻击?
攻击条件
低加密指数攻击要成立,需要同时满足:
- 公钥指数 e 很小(如 e=3)
- 明文 m 很小 ,使得 m e < n m^e < n me<n
当 m e < n m^e < n me<n 时,加密公式退化为:
c = m e c = m^e c=me
没有模 n 的"折返",c 就是一个完全的 e 次幂。
为什么不用模 n?
RSA 的安全性来自模 n 的"折返"作用:
- 模 n 把大数"折叠"到 0 , n − 1 0, n-1 0,n−1 范围内
- 这个"折叠"使得从 c 反推 m 变得困难(需要分解 n)
但如果 m e < n m^e < n me<n,则:
c = m e m o d n = m e ( 因为 m e < n ) c = m^e \bmod n = m^e \quad (\text{因为 } m^e < n) c=memodn=me(因为 me<n)
此时 c 没有被"折叠",直接开方即可。
什么时候会出现 m e < n m^e < n me<n?
这取决于 e 和 n 的大小关系:
- e=3 :只要 m < n 3 m < \sqrt3{n} m<3n ,就有 m 3 < n m^3 < n m3<n
- e=5 :只要 m < n 5 m < \sqrt5{n} m<5n ,就有 m 5 < n m^5 < n m5<n
而 n 通常是几百位的十进制数,所以:
- 对于 e=3,只要明文 m 小于 n 3 \sqrt3{n} 3n (大约是 n 的 1/3 位数十进制),攻击就成立
- 如果明文是短字符串(如 flag{...}),往往满足条件
简单例子
我们用 p=3, q=11 来演示,数字小到可以在草稿纸上算完。
生成密钥
- n = p × q = 3 × 11 = 33
- φ(n) = (p-1)(q-1) = 2 × 10 = 20
- 选 e = 3(gcd(3, 20) = 1 ✓),公钥 (e, n) = (3, 33)
加密小明文
场景一:明文很小,m^e < n
要加密明文 m = 2:
- c = m^e mod n = 2^3 mod 33 = 8
- 检查:2^3 = 8 < 33 ✓ 没有"折返"!
- 所以 c = 8 就是 2^3 的精确值
攻击:直接开方
- m = √3{c} = √3{8} = 2 ✓ 恢复明文!
场景二:明文稍大,m^e > n
要加密明文 m = 4:
- c = m^e mod n = 4^3 mod 33
- 4^3 = 64 > 33,发生"折返"
- 64 mod 33 = 64 - 33 = 31
- 所以 c = 31
攻击:直接开方失败
- √3{31} ≈ 3.14,不是整数
- 因为 31 是 64 mod 33 的结果,不是完全立方数
- 此时需要用其他方法(如小 n 分解)
代码实现
python
# ============================================================
# RSA 低加密指数攻击(手算例子)
# ============================================================
#
# 场景: n=33, e=3
# m=2 时: c=8, m^3 < n, 直接开方 m=∛8=2 ✓
# m=4 时: c=31, m^3 > n, 开方失败,需要分解 n
# ============================================================
def integer_nth_root(x, n):
"""
计算 x 的 n 次整数根(精确)
返回整数 r 使得 r^n <= x < (r+1)^n
如果 x 不是完全 n 次幂,返回 None
"""
if x < 0:
return None
if x == 0:
return 0
# 二分查找
low, high = 1, x
while low <= high:
mid = (low + high) // 2
mid_n = mid ** n
if mid_n == x:
return mid
elif mid_n < x:
low = mid + 1
else:
high = mid - 1
return None # 不是完全 n 次幂
def main():
# ---- 公钥 ----
n = 33
e = 3
print("=== RSA 低加密指数攻击 ===")
print(f"公钥 (e, n) = ({e}, {n})")
print(f"注意: e = {e} 很小,可能存在低加密指数攻击")
print()
# ============ 场景一:m^e < n ============
print("=== 场景一:m=2, m^3 < n ===")
m1 = 2
c1 = pow(m1, e, n)
print(f"明文 m = {m1}")
print(f"密文 c = m^e mod n = {m1}^{e} mod {n} = {c1}")
print(f"检查: m^e = {m1}^{e} = {m1**e} < {n}? {m1**e < n}")
# 攻击:直接开方
m1_recovered = integer_nth_root(c1, e)
if m1_recovered is not None:
print(f"攻击: m = ∛c = ∛{c1} = {m1_recovered} ✓ 成功!")
else:
print(f"攻击: ∛{c1} 不是整数,攻击失败")
print()
# ============ 场景二:m^e > n ============
print("=== 场景二:m=4, m^3 > n ===")
m2 = 4
c2 = pow(m2, e, n)
print(f"明文 m = {m2}")
print(f"密文 c = m^e mod n = {m2}^{e} mod {n} = {c2}")
print(f"检查: m^e = {m2}^{e} = {m2**e} < {n}? {m2**e < n}")
# 攻击:直接开方
m2_recovered = integer_nth_root(c2, e)
if m2_recovered is not None:
print(f"攻击: m = ∛c = ∛{c2} = {m2_recovered} ✓ 成功!")
else:
print(f"攻击: ∛{c2} ≈ {c2**(1/3):.2f},不是整数,攻击失败")
print(f" 因为 m^e = {m2**e} > {n},发生了模 n 折返")
print()
# ============ 总结 ============
print("=== 总结 ===")
print("低加密指数攻击成功的条件:")
print(f" 1. e 很小(如 e={e})")
print(f" 2. 明文 m 很小,使得 m^e < n")
print(f" 3. 密文 c = m^e(没有发生模 n 折返)")
print()
print("当满足条件时,c 就是完全 e 次幂,直接开方即可恢复 m。")
if __name__ == "__main__":
main()
运行结果:
bash
=== RSA 低加密指数攻击 ===
公钥 (e, n) = (3, 33)
注意: e = 3 很小,可能存在低加密指数攻击
=== 场景一:m=2, m^3 < n ===
明文 m = 2
密文 c = m^e mod n = 2^3 mod 33 = 8
检查: m^e = 2^3 = 8 < 33? True
攻击: m = ∛c = ∛8 = 2 ✓ 成功!
=== 场景二:m=4, m^3 > n ===
明文 m = 4
密文 c = m^e mod n = 4^3 mod 33 = 31
检查: m^e = 4^3 = 64 < 33? False
攻击: ∛31 ≈ 3.14,不是整数,攻击失败
因为 m^e = 64 > 33,发生了模 n 折返
=== 总结 ===
低加密指数攻击成功的条件:
1. e 很小(如 e=3)
2. 明文 m 很小,使得 m^e < n
3. 密文 c = m^e(没有发生模 n 折返)
当满足条件时,c 就是完全 e 次幂,直接开方即可恢复 m。
扩展:当 m 稍大时怎么办?
如果 m e m^e me 稍大于 n n n,即 m e = c + k ⋅ n m^e = c + k \cdot n me=c+k⋅n(k 很小),可以:
- 枚举 k = 0, 1, 2, ...
- 计算 m e = c + k ⋅ n m^e = c + k \cdot n me=c+k⋅n
- 检查 m e m^e me 是否为完全 e 次幂
- 如果是,则 m = c + k ⋅ n e m = \sqrte{c + k \cdot n} m=ec+k⋅n
这就是 Coppersmith 低加密指数攻击 的简化版。
公式:
m = c + k ⋅ n e m = \sqrte{c + k \cdot n} m=ec+k⋅n
其中 k 从 0 开始枚举,直到找到整数解。
总结
低加密指数攻击的关键:
- 识别条件:e 很小(如 e=3),且明文 m 很小
- 检查方法:尝试对密文 c 直接开 e 次方根
- 扩展方法:如果直接开方失败,枚举 k,用 Coppersmith 方法求解
在实际 CTF 比赛中,看到 e=3 或 e=5 这样的小指数,第一时间就应该想到低加密指数攻击!
作业1:低加密指数攻击(e=3)
题目

n = 0x52d483c27cd806550fbe0e37a61af2e7cf5e0efb723dfc81174c918a27627779b21fa3c851e9e94188eaee3d5cd6f752406a43fbecb53e80836ff1e185d3ccd7782ea846c2e91a7b0808986666e0bdadbfb7bdd65670a589a4d2478e9adcafe97c6ee23614bcb2ecc23580f4d2e3cc1ecfec25c50da4bc754dde6c8bfd8d1fc16956c74d8e9196046a01dc9f3024e11461c294f29d7421140732fedacac97b8fe50999117d27943c953f18c4ff4f8c258d839764078d4b6ef6e8591e0ff5563b31a39e6374d0d41c8c46921c25e5904a817ef8e39e5c9b71225a83269693e0b7e3218fc5e5a1e8412ba16e588b3d6ac536dce39fcdfce81eec79979ea6872793
e = 0x3
c = 0x10652cdfaa6b63f6d7bd1109da08181e500e5643f5b240a9024bfa84d5f2cac9310562978347bb232d63e7289283871efab83d84ff5a7b64a94a79d34cfbd4ef121723ba1f663e514f83f6f01492b4e13e1bb4296d96ea5a353d3bf2edd2f449c03c4a3e995237985a596908adc741f32365
求明文作为 flag 提交。
解题过程
第一步:分析已知条件
- 已知 n(十六进制),e=3,c(十六进制)
- e=3 很小,怀疑是低加密指数攻击
- 需要检查 c 是否为完全立方数
第二步:将十六进制转为十进制
python
n = 0x52d483c27cd806550fbe0e37a61af2e7cf5e0efb723dfc81174c918a27627779b21fa3c851e9e94188eaee3d5cd6f752406a43fbecb53e80836ff1e185d3ccd7782ea846c2e91a7b0808986666e0bdadbfb7bdd65670a589a4d2478e9adcafe97c6ee23614bcb2ecc23580f4d2e3cc1ecfec25c50da4bc754dde6c8bfd8d1fc16956c74d8e9196046a01dc9f3024e11461c294f29d7421140732fedacac97b8fe50999117d27943c953f18c4ff4f8c258d839764078d4b6ef6e8591e0ff5563b31a39e6374d0d41c8c46921c25e5904a817ef8e39e5c9b71225a83269693e0b7e3218fc5e5a1e8412ba16e588b3d6ac536dce39fcdfce81eec79979ea6872793
e = 3
c = 0x10652cdfaa6b63f6d7bd1109da08181e500e5643f5b240a9024bfa84d5f2cac9310562978347bb232d63e7289283871efab83d84ff5a7b64a94a79d34cfbd4ef121723ba1f663e514f83f6f01492b4e13e1bb4296d96ea5a353d3bf2edd2f449c03c4a3e995237985a596908adc741f32365
第三步:尝试直接开立方
检查 c 是否为完全立方数:
python
m = integer_nth_root(c, 3)
如果是整数,则攻击成功!
第四步:如果不是完全立方数
如果直接开方失败,说明 m e ≥ n m^e \ge n me≥n,尝试 Coppersmith 方法:
python
for k in range(0, 100): # 枚举 k
m_e = c + k * n
m = integer_nth_root(m_e, 3)
if m is not None:
print(f"找到 k={k}, m={m}")
break
第五步:将明文整数转为字节串
python
m_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
print(m_bytes.decode('utf-8'))
具体实现代码
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
低加密指数攻击
使用精确的整数开方方法
"""
def integer_nth_root(x, n):
"""
计算 x 的 n 次整数根(精确,使用二分查找)
返回整数 r 使得 r^n == x
如果 x 不是完全 n 次幂,返回 None
"""
if x < 0:
return None
if x == 0:
return 0
if x == 1:
return 1
# 二分查找
low = 1
# 估算上界:x 的位数除以 n
# 对于立方根,上界约为 2^(bits/3)
high = 1 << ((x.bit_length() // n) + 2)
while low <= high:
mid = (low + high) // 2
mid_n = mid ** n
if mid_n == x:
return mid
elif mid_n < x:
low = mid + 1
else:
high = mid - 1
return None
def main():
# ---- 题目参数(十六进制)----
n_hex = "52d483c27cd806550fbe0e37a61af2e7cf5e0efb723dfc81174c918a27627779b21fa3c851e9e94188eaee3d5cd6f752406a43fbecb53e80836ff1e185d3ccd7782ea846c2e91a7b0808986666e0bdadbfb7bdd65670a589a4d2478e9adcafe97c6ee23614bcb2ecc23580f4d2e3cc1ecfec25c50da4bc754dde6c8bfd8d1fc16956c74d8e9196046a01dc9f3024e11461c294f29d7421140732fedacac97b8fe50999117d27943c953f18c4ff4f8c258d839764078d4b6ef6e8591e0ff5563b31a39e6374d0d41c8c46921c25e5904a817ef8e39e5c9b71225a83269693e0b7e3218fc5e5a1e8412ba16e588b3d6ac536dce39fcdfce81eec79979ea6872793"
e = 3
c_hex = "10652cdfaa6b63f6d7bd1109da08181e500e5643f5b240a9024bfa84d5f2cac9310562978347bb232d63e7289283871efab83d84ff5a7b64a94a79d34cfbd4ef121723ba1f663e514f83f6f01492b4e13e1bb4296d96ea5a353d3bf2edd2f449c03c4a3e995237985a596908adc741f32365"
# 转为十进制
n = int(n_hex, 16)
c = int(c_hex, 16)
print("===低加密指数攻击(e=3)===")
print(f"e = {e}")
print(f"n(位数)= {n.bit_length()} bits")
print(f"c(位数)= {c.bit_length()} bits")
print()
# ---- 方法一:直接开立方 ----
print("=== 方法一:直接开立方 ===")
print("假设 m^3 < n,即密文 c 没有发生模 n 折返")
# 检查 c < n
print(f"检查: c < n? {c < n}")
if c < n:
print(" c < n,说明可能 m^3 < n,可以尝试直接开立方")
else:
print(" c >= n,说明 m^3 > n,需要使用 Coppersmith 方法")
print()
# 尝试开立方(使用精确的二分查找)
print("尝试 m = ∛c (使用二分查找精确计算)...")
m = integer_nth_root(c, 3)
if m is not None:
print(f"✓ c 是完全立方数!")
print(f"m = ∛c = {m}")
# 验证
print(f"\n验证:")
m_cubed = m ** 3
print(f" m^3 = {m_cubed}")
print(f" c = {c}")
print(f" m^3 == c? {m_cubed == c}")
if m_cubed == c:
# 转为字节串
print(f"\n将明文整数转为字节串:")
m_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
print(f"明文(字节串): {m_bytes}")
try:
decoded = m_bytes.decode('utf-8')
print(f"明文(字符串): {decoded}")
print(f"\n✓ 找到 flag: {decoded}")
except UnicodeDecodeError:
print(f"明文(hex): {m_bytes.hex()}")
else:
print("✗ 验证失败,m^3 != c")
else:
print(f"✗ c 不是完全立方数")
print()
# ---- 方法二:Coppersmith 方法(枚举 k)----
print("=== 方法二:Coppersmith 方法(枚举 k)===")
print("假设 m^3 = c + k*n,枚举 k 求解")
print()
found = False
max_k = 100 # 最多枚举 100 次
print(f"开始枚举 k 从 0 到 {max_k-1}...")
for k in range(0, max_k):
m_e = c + k * n
m = integer_nth_root(m_e, 3)
if m is not None:
print(f"\n✓ 找到解!k = {k}")
print(f"m = ∛(c + k*n) = {m}")
# 验证
print(f"\n验证:")
m_cubed = m ** 3
print(f" m^3 == c + k*n? {m_cubed == m_e}")
if m_cubed == m_e:
# 转为字节串
print(f"\n将明文整数转为字节串:")
m_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
print(f"明文(字节串): {m_bytes}")
try:
decoded = m_bytes.decode('utf-8')
print(f"明文(字符串): {decoded}")
print(f"\n✓ 找到 flag: {decoded}")
except UnicodeDecodeError:
print(f"明文(hex): {m_bytes.hex()}")
found = True
break
else:
print(" 验证失败,继续枚举...")
if not found:
print(f"\n✗ 枚举 k=0..{max_k-1} 均未找到解")
print(" 可能需要更大的 k 或其他方法")
if __name__ == "__main__":
main()
运行结果
bash
=== 低加密指数攻击(e=3)===
e = 3
n(位数)= 2047 bits
c(位数)= 909 bits
=== 方法一:直接开立方 ===
假设 m^3 < n,即密文 c 没有发生模 n 折返
检查: c < n? True
c < n,说明可能 m^3 < n,可以尝试直接开立方
尝试 m = ∛c (使用二分查找精确计算)...
✓ c 是完全立方数!
m = ∛c = 13040004482819713819817340524563023159919305047824600478799740488797710355579494486728991357
验证:
m^3 = 2217344750798296091193230394221582894657909643174934416842588335871298152598368701484028832407289746218387783855373449002121088413603751014125921242419602155087438902181522441026460003722677539409576093794862185483713606547386172606576925933695952279401957552813065318376293
c = 2217344750798296091193230394221582894657909643174934416842588335871298152598368701484028832407289746218387783855373449002121088413603751014125921242419602155087438902181522441026460003722677539409576093794862185483713606547386172606576925933695952279401957552813065318376293
m^3 == c? True
将明文整数转为字节串:
明文(字节串): b'flag{25df8caf006ee5db94d48144c33b2c3b}'
明文(字符串): flag{25df8caf006ee5db94d48144c33b2c3b}
✓ 找到 flag: flag{25df8caf006ee5db94d48144c33b2c3b}
答案
flag{25df8caf006ee5db94d48144c33b2c3b}
作业2:与作业1方式相同
题目

python
from Crypto.Util.number import *
import binascii
import gmpy2
flag = '*********************'
hex_flag=int(flag.encode("hex"),16)
p=getPrime(512)
q=getPrime(512)
n=p*q
e=0x3
c=pow(hex_flag,e,n)
print("n=",hex(n))
print("e=",hex(e))
print("c=",hex(c))
'''
('n=', '0x86f4be77b79e166a6311e7982ba2e5ff479db93a01c56034479a9e35382293c35769da222974e9425829099aa4fe4f41185283866202042b356194bab312e6ed2fb0b10b1b74767dc1cc5306872d33b1f3b75612c594751ec70e4cf5fccc6fceafe0401648869cc40425a176ab70286d92a29dfd675f2384c9383e0a9750b25bL')
('e=', '0x3')
('c=', '0x10652cdf7ed2bc53f58b321f476c3a3cf3281e541f4d533a73a0fcbf525230f2e01c183dee660676317ea99250202548e5525b0c14adbeb77d4fa7e2e1d339L')
'''
解题过程
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
低加密指数攻击(e=3)
使用精确的整数开方方法
"""
def integer_nth_root(x, n):
"""
计算 x 的 n 次整数根(精确,使用二分查找)
返回整数 r 使得 r^n == x
如果 x 不是完全 n 次幂,返回 None
"""
if x < 0:
return None
if x == 0:
return 0
if x == 1:
return 1
# 二分查找
low = 1
# 估算上界:x 的位数除以 n
# 对于立方根,上界约为 2^(bits/3)
high = 1 << ((x.bit_length() // n) + 2)
while low <= high:
mid = (low + high) // 2
mid_n = mid ** n
if mid_n == x:
return mid
elif mid_n < x:
low = mid + 1
else:
high = mid - 1
return None
def main():
# ---- 题目参数(十六进制)----
n_hex = "86f4be77b79e166a6311e7982ba2e5ff479db93a01c56034479a9e35382293c35769da222974e9425829099aa4fe4f41185283866202042b356194bab312e6ed2fb0b10b1b74767dc1cc5306872d33b1f3b75612c594751ec70e4cf5fccc6fceafe0401648869cc40425a176ab70286d92a29dfd675f2384c9383e0a9750b25b"
e = 3
c_hex = "10652cdf7ed2bc53f58b321f476c3a3cf3281e541f4d533a73a0fcbf525230f2e01c183dee660676317ea99250202548e5525b0c14adbeb77d4fa7e2e1d339"
# 转为十进制
n = int(n_hex, 16)
c = int(c_hex, 16)
print("=== 低加密指数攻击(e=3)===")
print(f"e = {e}")
print(f"n(位数)= {n.bit_length()} bits")
print(f"c(位数)= {c.bit_length()} bits")
print()
# ---- 方法一:直接开立方 ----
print("=== 方法一:直接开立方 ===")
print("假设 m^3 < n,即密文 c 没有发生模 n 折返")
# 检查 c < n
print(f"检查: c < n? {c < n}")
if c < n:
print(" c < n,说明可能 m^3 < n,可以尝试直接开立方")
else:
print(" c >= n,说明 m^3 > n,需要使用 Coppersmith 方法")
print()
# 尝试开立方(使用精确的二分查找)
print("尝试 m = ∛c (使用二分查找精确计算)...")
m = integer_nth_root(c, 3)
if m is not None:
print(f"✓ c 是完全立方数!")
print(f"m = ∛c = {m}")
# 验证
print(f"\n验证:")
m_cubed = m ** 3
print(f" m^3 = {m_cubed}")
print(f" c = {c}")
print(f" m^3 == c? {m_cubed == c}")
if m_cubed == c:
# 转为字节串
print(f"\n将明文整数转为字节串:")
m_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
print(f"明文(字节串): {m_bytes}")
try:
decoded = m_bytes.decode('utf-8')
print(f"明文(字符串): {decoded}")
print(f"\n✓ 找到 flag: {decoded}")
except UnicodeDecodeError:
print(f"明文(hex): {m_bytes.hex()}")
else:
print("✗ 验证失败,m^3 != c")
else:
print(f"✗ c 不是完全立方数")
print()
# ---- 方法二:Coppersmith 方法(枚举 k)----
print("=== 方法二:Coppersmith 方法(枚举 k)===")
print("假设 m^3 = c + k*n,枚举 k 求解")
print()
found = False
max_k = 100 # 最多枚举 100 次
print(f"开始枚举 k 从 0 到 {max_k-1}...")
for k in range(0, max_k):
m_e = c + k * n
m = integer_nth_root(m_e, 3)
if m is not None:
print(f"\n✓ 找到解!k = {k}")
print(f"m = ∛(c + k*n) = {m}")
# 验证
print(f"\n验证:")
m_cubed = m ** 3
print(f" m^3 == c + k*n? {m_cubed == m_e}")
if m_cubed == m_e:
# 转为字节串
print(f"\n将明文整数转为字节串:")
m_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
print(f"明文(字节串): {m_bytes}")
try:
decoded = m_bytes.decode('utf-8')
print(f"明文(字符串): {decoded}")
print(f"\n✓ 找到 flag: {decoded}")
except UnicodeDecodeError:
print(f"明文(hex): {m_bytes.hex()}")
found = True
break
else:
print(" 验证失败,继续枚举...")
if not found:
print(f"\n✗ 枚举 k=0..{max_k-1} 均未找到解")
print(" 可能需要更大的 k 或其他方法")
if __name__ == "__main__":
main()
运行结果
bash
=== 低加密指数攻击(e=3)===
e = 3
n(位数)= 1024 bits
c(位数)= 501 bits
=== 方法一:直接开立方 ===
假设 m^3 < n,即密文 c 没有发生模 n 折返
检查: c < n? True
c < n,说明可能 m^3 < n,可以尝试直接开立方
尝试 m = ∛c (使用二分查找精确计算)...
✓ c 是完全立方数!
m = ∛c = 149691910166879983220013334339944014369691538367017
验证:
m^3 = 3354246620726085986568668128373090812593945499332956611216503173394622300212425527028396698978553852269462669721310427515531023131901147683787903193913
c = 3354246620726085986568668128373090812593945499332956611216503173394622300212425527028396698978553852269462669721310427515531023131901147683787903193913
m^3 == c? True
将明文整数转为字节串:
明文(字节串): b'flag is md5("SmallM")'
明文(字符串): flag is md5("SmallM")
✓ 找到 flag: flag is md5("SmallM")
