1. 解题背景与题目概述
最近参加了一场网络安全竞赛,其中一道密码学题目"Try E"引起了我的兴趣。这道题考察的是RSA加密系统中公钥指数e过大时的安全问题,具体来说就是Wiener攻击的应用场景。题目给出了一个非常规的RSA加密实现,要求我们从给定的N、e、c中恢复出明文flag。
2. 题目代码分析
首先我们来看题目提供的Python代码:
python复制from Crypto.Util.number import getPrime, bytes_to_long
from secret import flag
def get_huge_RSA():
p = getPrime(1024)
q = getPrime(1024)
N = p * q
phi = (p - 1) * (q - 1)
while True:
d = getPrime(256)
e = pow(d, -1, phi)
if e.bit_length() == N.bit_length():
break
return N,e
if __name__ == '__main__':
N, e = get_huge_RSA()
m = bytes_to_long(flag)
c = pow(m, e, N)
print(f'N = {hex(N)}')
print(f'e = {hex(e)}')
print(f'c = {hex(c)}')
这段代码有几个关键特点:
- 生成两个1024位的素数p和q,计算N=p*q
- 生成一个256位的素数d作为私钥
- 计算e作为d关于φ(N)的模逆
- 特别要求e的位长度必须与N相同(即e很大)
- 最后用公钥(N,e)加密flag
3. 密码学原理分析
3.1 RSA基础知识回顾
在标准RSA中:
- 公钥:(N, e)
- 私钥:d
- 加密:c ≡ m^e mod N
- 解密:m ≡ c^d mod N
其中e和d满足:e*d ≡ 1 mod φ(N)
3.2 题目特殊之处
这道题的特殊性在于:
- 私钥d非常小(只有256位),而N是2048位
- 公钥e非常大(2048位),与N同数量级
- 这种e很大而d很小的情况正好符合Wiener攻击的适用条件
4. Wiener攻击原理
4.1 数学基础
Wiener攻击基于连分数理论,核心思想是当d足够小时,k/d可以作为e/N的一个近似。
从RSA关系式:
ed = 1 + kφ(N)
因为φ(N) ≈ N,所以:
e/N ≈ k/d
当d < N^(1/4)/3时,k/d会出现在e/N的连分数展开的收敛子中。
4.2 攻击步骤
- 计算e/N的连分数展开
- 检查每个收敛子k/d是否满足:
- d是整数
- (e*d - 1)能被k整除
- 由φ=(e*d-1)/k能有效分解N
- 找到正确的d后即可解密
5. 解题实现
5.1 连分数实现
python复制def continued_fraction(e, n):
cf = []
while n:
q = e // n
cf.append(q)
e, n = n, e - q * n
return cf
def convergents(cf):
convergents = []
for i in range(len(cf)):
num = cf[i]
den = 1
for j in range(i-1, -1, -1):
num, den = cf[j] * num + den, num
convergents.append((num, den))
return convergents
5.2 完整攻击代码
python复制import gmpy2
from Crypto.Util.number import long_to_bytes
N = 0x662854e5ee8b1aa73eea7c897f0f1bd7cace486dea68fb4e9b1affe86ddae225221e9941b7e90b7dd87d57988fc3428f51433a5c2a6e7ef9cbe85aace0925914347ca1d403ea58e2f36435b67648f8caf0abd29c9c24d3caeadab2c41522deda75c19584ec917fa683ff16c932f334db3145a8367c3dc6bc3b918ff3f69f8bfb16c45b4caab1e8ecef24e8e923e984e921115d9fb997a638c8e25d74d592f279359e7147745a7a8443603287120d1a186f30d5a41ce26545f85844721b788564e306791ae39c3be23aeeab010e79302afab4b3e9ab18cb2769382ff8fcbc0514f51861ec6db247f0a0343b7cc6d44299878f7006c118df10de6937c11e3aed7d
e = 0x58a2680eae331e41397475dd699a75f242897e4ed4048338137eb40100cc406b651c4518f4057ad8419cd6a82605113dd5801cd9f022f8bda424b02db5feb333d96636026c3ffc4cab74f7426aa14fb1139663a4f6248dd8e5c7075fcdf3e520c425697775cfb65d33ccca5ffe08d944753b1e9da2dbf96713ece5436deb6dbc843dcd5c497eda9919e055a32c76798770535c6a91ae00b971f35be1ab9e48dd4c701026e0744826001f6fb30e4f68d6e4981aa5a5bbcc995a9e46a4d9b1658348d0fb3b1314fa091251ea1b7379a854a3860fcba2ace323dca8157008d80d6035fd6c880404495f933bf4b4ae829b35823450a921f64b9cf63ae861b3fc4ef7
c = 0x47d2e297294af43a9a02d465f7f5272cab0af2445cbc6022def1098e075dcfb3a7830f09df6112a9fa55b34ed4d0baebad54ea2cbd32e4367cbe7a138409a0ef4c36d837ea7817ec3624fca3a19c1377eaf08e4a519de73cb2c5e99ec8f3998e04d4c3bc44a6f1eb389111bf7c72c68bf1dd743e656467d1ecdd314b37313963758634b83ea96724b1872367a922788f2c8a046c76ccc57e86686bedd7ac431f92b9e2f1fae79701fa0d14d2a0119860c8908336c6caec87b9733f626166373631e1e7e9ba6be92d712e84e821e0e4dc105d460c6640498aefaeb5146d0f57b8e57c3e24bc13f3e79082172c1690428eb49bc6035f1e60f6a579129a2da00c60
def wiener_attack(e, n):
def continued_fraction(e, n):
cf = []
while n:
q = e // n
cf.append(q)
e, n = n, e - q * n
return cf
def convergents(cf):
convergents = []
for i in range(len(cf)):
num = cf[i]
den = 1
for j in range(i-1, -1, -1):
num, den = cf[j] * num + den, num
convergents.append((num, den))
return convergents
cf = continued_fraction(e, n)
convs = convergents(cf)
for k, d in convs:
if k == 0:
continue
if (e * d - 1) % k != 0:
continue
phi = (e * d - 1) // k
s = n - phi + 1
D = s*s - 4*n
if D < 0:
continue
sqrtD = gmpy2.isqrt(D)
if sqrtD * sqrtD == D:
p = (s + sqrtD) // 2
q = (s - sqrtD) // 2
if p * q == n:
return d, int(p), int(q)
return None
res = wiener_attack(e, N)
if res:
d, p, q = res
print(f"Found d = {d}")
print(f"p = {p}")
print(f"q = {q}")
m = pow(c, d, N)
print("Flag:", long_to_bytes(m).decode())
else:
print("Wiener attack failed")
6. 攻击结果分析
运行上述代码后,我们成功获取到了:
- 私钥d
- 素数p和q
- 最终解密出的flag
这个结果验证了Wiener攻击在d较小时的有效性。值得注意的是,题目中d的位数(256)远小于N^(1/4)/3 ≈ 2^512,完全满足攻击条件。
7. 防御措施
为了防止这类攻击,在实际RSA应用中应该:
- 避免使用过小的私钥d
- 可以使用较大的e(如65537)
- 确保d的位数至少为N的位数的1/2
8. 扩展思考
这道题展示了密码学实现中参数选择的重要性。即使算法本身是安全的,不当的参数选择也会引入漏洞。在实际开发中,应该:
- 使用标准库而非自己实现加密算法
- 遵循已知的安全参数建议
- 对关键参数进行安全检查
通过这道CTF题目,我们不仅学习了Wiener攻击的具体实现,更重要的是理解了RSA参数选择的安全意义。