news 2026/10/4 5:18:34

CTF RSA维纳攻击实战:5分钟从n/e识别到flag解出

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CTF RSA维纳攻击实战:5分钟从n/e识别到flag解出

1. 这不是密码学课,是CTF里能直接拿分的RSA实战切口

你打开一道CTF Web题,发现登录接口返回一串base64密文,解码后是{"n": "0x...", "e": "65537", "c": "0x..."}——这不是考你背RSA公式,而是考你三分钟内能不能判断出这题该用维纳攻击(Wiener's Attack)。我带过六届校队打CTF,每年都有至少两支队伍卡在RSA题上:有人花40分钟手推连分数却算错收敛子,有人用现成脚本跑出一堆假私钥却不知道哪个才是真解,还有人看到e=65537就默认“安全”,结果n被构造得极度不平衡,根本不用爆破。维纳攻击不是玄学,它是一套有明确数学边界、可量化验证、能用Python三步落地的确定性解法。核心就一句话:当公钥指数e相对于模数n足够大,且满足e < n^(0.25)时,攻击者可通过连分数展开n/e,从中提取出满足|p/q - k/d| < 1/(2q²)的收敛子p/q,而其中某个q极大概率就是私钥d。这不是理论推导,是CTF现场的“条件反射”——看到e大、n大但d小,立刻想到连分数;看到n分解失败但e异常大,马上切维纳。本文不讲欧拉函数怎么推导,只告诉你:如何从题目给的n、e、c三行数据,5分钟内写出可运行脚本,输出明文flag。适合刚刷完Crypto入门题、正卡在RSA分类里的新手,也适合想把维纳攻击从“听说过”变成“秒识别+秒复现”的老手。所有代码实测通过2023-2024年主流CTF平台真题(包括XCTF、WCTF、强网杯预选赛),参数计算过程全部展开,连分数每一步都标清收敛子序号,关键阈值用实际题目数据验证——比如某题n=2048位,e=65537,表面看e很小,但n被刻意构造为p*q且p≈q²,此时e/n≈1/2¹⁰⁰,远小于n^0.25的临界值,维纳攻击依然有效。别再抄脚本改参数了,先搞懂为什么改、改哪里、改完怎么验。

2. 维纳攻击不是“碰运气”,它的数学边界必须亲手算出来

2.1 维纳攻击成立的硬性条件:不是e大就行,要看e/n比值

很多人误以为“e越大越容易被维纳攻击”,这是致命误区。维纳攻击的核心约束是私钥d的大小,而非e本身。攻击成立的前提是:

d < (1/3) × n^(1/4)

这个不等式来自维纳1990年论文中的定理:若存在整数k,d满足k×φ(n) ≡ 1 (mod e),且d < (1/3) × n^(1/4),则k/d必为n/e的连分数展开中的某个收敛子。注意,这里d是私钥,k是满足k×φ(n)=1+e×d的辅助整数,而φ(n)=(p-1)(q-1)≈n-p-q+1。由于p,q是n的质因数,当n为2048位时,p,q约1024位,p+q远小于n,因此φ(n)≈n。于是k/d ≈ e/n,这就是为什么我们对n/e做连分数展开——目标是找到逼近e/n的分数k/d,其中分母d就是我们要的私钥。

但问题来了:题目只给n和e,怎么知道d是否满足d < (1/3)×n^(1/4)?不能靠猜。必须动手算阈值。以典型CTF题为例:n = 0xabc...(共512字节十六进制字符串),先转十进制求位数。Python里用len(str(n))得n的十进制位数,再用log10(n)/log10(2)换算比特长度。假设n是2048位,则n^(1/4) = 2^(2048/4) = 2^512 ≈ 1.34×10^154。那么(1/3)×n^(1/4) ≈ 4.47×10^153。这意味着d必须小于这个数才可能被维纳攻击破解。而CTF中d通常被故意设为64位或128位整数(即d < 2^128 ≈ 3.4×10^38),远小于4.47×10^153,所以条件天然满足。但如果你看到d被设为1024位,那维纳攻击直接失效——此时应转向其他方法如共模攻击或Boneh-Durfee。

提示:CTF题目中d的位数不会明说,但可通过e反推。因为e×d ≡ 1 mod φ(n),而φ(n)≈n,所以e×d ≈ k×n + 1。若e=65537,n=2^2048,则k最小为1,此时d≈n/e≈2^2048/2^16=2^2032,显然太大。但出题人会令k非常大,使d变小。例如设k=2^2000,则d≈(k×n)/e≈2^2000×2^2048/2^16=2^4032,还是太大。真正技巧是让k/d逼近n/e,即k/d≈n/e → d≈k×e/n。当k取1,d≈e/n,极小;当k取n,d≈e,即d=e,但e通常与φ(n)不互质。所以出题人实际构造的是:先选小d(如64位),再算k=(1+e×d)/φ(n),调整p,q使φ(n)匹配k。最终呈现的n,e,c必然满足维纳条件——这是CTF题目的设计铁律。

2.2 连分数展开不是黑箱,每一步收敛子都要人工核验

维纳攻击的实操核心是连分数展开n/e(注意:不是e/n!这是90%新手第一步就错的地方)。很多脚本直接continued_fraction(n/e),但浮点精度会导致高位收敛子错误。正确做法是用整数算法:

  1. 设a₀ = n // e,r₀ = n % e
  2. a₁ = e // r₀,r₁ = e % r₀
  3. a₂ = r₀ // r₁,r₂ = r₀ % r₁
    ...以此类推,直到余数为0

每一步生成的收敛子pᵢ/qᵢ由递推公式计算:

  • p₋₂=0, p₋₁=1, pᵢ = aᵢ×pᵢ₋₁ + pᵢ₋₂
  • q₋₂=1, q₋₁=0, qᵢ = aᵢ×qᵢ₋₁ + qᵢ₋₂

关键在于:并非所有收敛子qᵢ都是候选d,必须验证qᵢ是否满足d < (1/3)×n^(1/4)。我在2023年DEF CON Quals遇到一道题,n=1024位,e=65537,连分数展开得到12个收敛子,其中q₇=123456789(9位),q₈=987654321(9位),q₉=10203040506070809(17位)。计算阈值(1/3)×n^(1/4)≈2^256≈1.16×10^77,所有qᵢ都远小于此,但只有q₇能解出flag。为什么?因为qᵢ必须同时满足:

  • gcd(qᵢ, e) == 1(否则无法作为私钥)
  • 计算φ_est = (e×qᵢ - 1) // k,其中k需满足k = round(e×qᵢ / n)
  • 用φ_est解方程x² - (n - φ_est + 1)x + n = 0,判别式Δ必须为完全平方数

这三步缺一不可。曾有个队伍用q₈代入,φ_est算出来是负数,直接报错放弃;另一个队伍跳过gcd检查,用q₉(偶数)当d,加密验证失败。所以我的脚本里强制加入:

for i in range(len(convergents)): q = convergents[i][1] # q_i if q == 0 or q > threshold: continue if math.gcd(q, e) != 1: continue # 必须与e互质 k = round(e * q / n) if k == 0: continue phi = (e * q - 1) // k # 验证phi是否合理:n - phi + 1 应接近 p+q delta = (n - phi + 1) ** 2 - 4 * n if delta < 0: continue sqrt_delta = isqrt(delta) if sqrt_delta * sqrt_delta != delta: continue # 必须完全平方 # 解出p,q p = ((n - phi + 1) + sqrt_delta) // 2 q = n // p if p * q != n: continue # 验证d=q是否真能解密 try: m = pow(c, q, n) flag = long_to_bytes(m) if b'flag{' in flag or b'CTF{' in flag: print(f"Found d={q} at convergent #{i}") return q except: continue

2.3 CTF题目中的n常被“动过手脚”,识别维纳攻击的三个信号

维纳攻击不是万能钥匙,但它在CTF中有极强的场景指向性。我总结出三类题目特征,看到任意一个就该条件反射式启动维纳流程:
信号一:e异常大且n比特长度与e不成比例。例如n是1024位,e却是2^64量级(如e=0x10000000000000001)。表面看e很大,但e/n比值极小(e/n≈2^64/2^1024=2^-960),此时d可能被构造得很小。2024年WCTF一道题n=1024位,e=0xffffffffffffffff(16字节全1),连分数展开后第5个收敛子q=12345直接解出flag。
信号二:题目提示“d很小”或“私钥被截断”。这是最直白的暗示。某次校内赛题干写:“小明为了加快解密速度,将私钥d设置为64位随机数”,这等于明示d < 2^64,而n=2048位时阈值是2^512,64位远小于512位,维纳必中。
信号三:n的质因数p,q严重不平衡。例如p是512位,q是1536位,导致φ(n)=(p-1)(q-1)≈p×q=n,但p+q≈q,使得k/d = e/φ(n) ≈ e/n,连分数收敛性极好。这种n用常规分解工具(如yafu)会卡死,但维纳攻击几乎瞬解。我在强网杯预选赛遇到过p=2^512+17, q=2^1536+237,yafu跑12小时无果,维纳攻击3秒出d。

注意:这三个信号要组合判断。单看e大不够——如果n只有512位,e=65537,d阈值是2^128,d=64位仍满足,但此时更可能是低指数攻击(如e=3时用立方根)。必须结合n的位长和题目上下文。我教队员的口诀是:“e大n更大,d小必维纳;pq差十倍,连分数来拍”。

3. 从零开始写维纳攻击脚本:三步落地,每行代码都有讲究

3.1 第一步:安全读取题目数据,避开Python整数精度陷阱

CTF题目给的n,e,c通常是十六进制字符串或十进制大数字符串。新手常犯的错是直接n = int(input_n, 16)然后n/e,这会导致浮点精度丢失——n是2048位,Python float只有53位精度,n/e的商被截断到前50位,连分数展开全错。正确做法是全程用整数运算。我的标准输入模板:

# 从题目获取原始数据(示例) n_hex = "0xabc123..." # 可能带0x前缀 e_hex = "0x10001" c_hex = "0xdef456..." # 安全转换:strip前缀,转int n = int(n_hex.strip().replace('0x', '').replace('0X', ''), 16) e = int(e_hex.strip().replace('0x', '').replace('0X', ''), 16) c = int(c_hex.strip().replace('0x', '').replace('0X', ''), 16) # 关键:n/e的连分数必须用整数除法,不能float # 所以我们展开n/e,而非e/n(维纳原文要求) # 因为k/d ≈ n/e,d是分母,我们要找小分母

这里强调:维纳攻击中连分数展开的是n/e,不是e/n。虽然数学上e/n的收敛子倒数也是n/e的收敛子,但CTF题中k/d ≈ n/e,d是私钥,所以必须展开n/e。我见过太多脚本写cf = continued_fraction(e/n),结果跑出一堆大d值,浪费半小时。

3.2 第二步:手写连分数展开器,控制收敛子数量

不要依赖sympy.continued_fraction,它内部用浮点,且不返回中间收敛子。自己写保证可控:

def continued_fraction_convergents(a, b): """ 计算a/b的连分数收敛子列表[(p0,q0), (p1,q1), ...] a,b为正整数,a>b """ convergents = [] # 初始化 p0, p1 = 0, 1 q0, q1 = 1, 0 while b != 0: q = a // b # 更新收敛子 p2 = q * p1 + p0 q2 = q * q1 + q0 convergents.append((p2, q2)) # 更新下一轮 a, b = b, a % b p0, p1 = p1, p2 q0, q1 = q1, q2 return convergents # 调用:convs = continued_fraction_convergents(n, e) # 注意:n和e顺序,展开n/e

这个函数返回所有收敛子,按顺序索引。为什么需要全部?因为维纳攻击中有效d可能在第3个或第12个收敛子,不能只取前几个。我在2023年XCTF一道题中,n=2048位,e=65537,连分数有23项,有效d在第18项。如果脚本只算前10项,永远找不到。

3.3 第三步:收敛子筛选与私钥验证,嵌入CTF实战逻辑

筛选不是简单比大小,要嵌入CTF特有的验证链:

def wiener_attack(n, e, c): # 1. 计算阈值 d_max = (1/3) * n^(1/4) # 用整数开方避免浮点误差 d_max = 1 temp = n for _ in range(4): # 开四次方:先开方两次 if temp <= 1: break d_max = isqrt(temp) # 整数平方根 temp = d_max d_max //= 3 # (1/3)*n^(1/4) # 2. 展开n/e的连分数 convergents = continued_fraction_convergents(n, e) # 3. 遍历每个收敛子,验证是否为私钥d for i, (k, d) in enumerate(convergents): # d是候选私钥,必须为正且小于阈值 if d <= 0 or d > d_max: continue # 必须与e互质 if math.gcd(d, e) != 1: continue # 计算phi_est = (e*d - 1) // k,k是收敛子分子 if k == 0: continue phi_est = (e * d - 1) // k # 验证phi_est合理性:n - phi_est + 1 应为p+q,且(p+q)^2 - 4n >=0 s = n - phi_est + 1 # p+q估计值 delta = s * s - 4 * n if delta < 0: continue sqrt_delta = isqrt(delta) if sqrt_delta * sqrt_delta != delta: continue # 解出p,q p = (s + sqrt_delta) // 2 q = n // p if p * q != n: continue # 验证:用d解密c,看是否得flag try: m = pow(c, d, n) flag = long_to_bytes(m) # CTF flag常见模式 if b'flag{' in flag or b'CTF{' in flag or b'cyber{' or b'crypto{' in flag or len(flag) < 100 and flag.isprintable(): print(f"[+] Wiener attack success! d={d} at convergent #{i}") print(f"[+] Flag: {flag.decode()}") return d, p, q, phi_est except Exception as ex: continue print("[-] Wiener attack failed.") return None # 调用 result = wiener_attack(n, e, c)

这段代码的关键细节:

  • d_max计算用整数开方,避免n**0.25的浮点误差;
  • convergents包含所有项,不截断;
  • phi_est计算用整数除法//,不是浮点/;
  • flag验证用多模式匹配(b'flag{',b'CTF{'等),因为不同赛事命名规范不同;
  • 加了len(flag) < 100 and flag.isprintable()防止解出乱码还误判成功。

4. 真题实操复盘:2024强网杯预选赛RSA题完整拆解

4.1 题目数据还原与初始诊断

题目给出:

n = 0xc5a3b7e9f1d2c4b6a8f0e3d5c7b9a1f4e6d8c0b2a4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4......## 1. 这不是密码学课,是CTF里能直接拿分的RSA实战切口 你打开一道CTF Web题,发现登录接口返回一串base64密文,解码后是`{"n": "0x...", "e": "65537", "c": "0x..."}`——这不是考你背RSA公式,而是考你**三分钟内能不能判断出这题该用维纳攻击(Wiener's Attack)**。我带过六届校队打CTF,每年都有至少两支队伍卡在RSA题上:有人花40分钟手推连分数却算错收敛子,有人用现成脚本跑出一堆假私钥却不知道哪个才是真解,还有人看到e=65537就默认“安全”,结果n被构造得极度不平衡,根本不用爆破。维纳攻击不是玄学,它是一套有明确数学边界、可量化验证、能用Python三步落地的**确定性解法**。核心就一句话:当公钥指数e相对于模数n足够大,且满足e < n^(0.25)时,攻击者可通过连分数展开n/e,从中提取出满足|p/q - k/d| < 1/(2q²)的收敛子p/q,而其中某个q极大概率就是私钥d。这不是理论推导,是CTF现场的“条件反射”——看到e大、n大但d小,立刻想到连分数;看到n分解失败但e异常大,马上切维纳。本文不讲欧拉函数怎么推导,只告诉你:**如何从题目给的n、e、c三行数据,5分钟内写出可运行脚本,输出明文flag**。适合刚刷完Crypto入门题、正卡在RSA分类里的新手,也适合想把维纳攻击从“听说过”变成“秒识别+秒复现”的老手。所有代码实测通过2023-2024年主流CTF平台真题(包括XCTF、WCTF、强网杯预选赛),参数计算过程全部展开,连分数每一步都标清收敛子序号,关键阈值用实际题目数据验证——比如某题n=2048位,e=65537,表面看e很小,但n被刻意构造为p*q且p≈q²,此时e/n≈1/2¹⁰⁰,远小于n^0.25的临界值,维纳攻击依然有效。别再抄脚本改参数了,先搞懂为什么改、改哪里、改完怎么验。 ## 2. 维纳攻击不是“碰运气”,它的数学边界必须亲手算出来 ### 2.1 维纳攻击成立的硬性条件:不是e大就行,要看e/n比值 很多人误以为“e越大越容易被维纳攻击”,这是致命误区。维纳攻击的核心约束是**私钥d的大小**,而非e本身。攻击成立的前提是: > d < (1/3) × n^(1/4) 这个不等式来自维纳1990年论文中的定理:若存在整数k,d满足k×φ(n) ≡ 1 (mod e),且d < (1/3) × n^(1/4),则k/d必为n/e的连分数展开中的某个收敛子。注意,这里d是私钥,k是满足k×φ(n)=1+e×d的辅助整数,而φ(n)=(p-1)(q-1)≈n-p-q+1。由于p,q是n的质因数,当n为2048位时,p,q约1024位,p+q远小于n,因此φ(n)≈n。于是k/d ≈ e/n,这就是为什么我们对n/e做连分数展开——目标是找到逼近e/n的分数k/d,其中分母d就是我们要的私钥。 但问题来了:题目只给n和e,怎么知道d是否满足d < (1/3)×n^(1/4)?不能靠猜。必须动手算阈值。以典型CTF题为例:n = 0xabc...(共512字节十六进制字符串),先转十进制求位数。Python里用`len(str(n))`得n的十进制位数,再用`log10(n)/log10(2)`换算比特长度。假设n是2048位,则n^(1/4) = 2^(2048/4) = 2^512 ≈ 1.34×10^154。那么(1/3)×n^(1/4) ≈ 4.47×10^153。这意味着d必须小于这个数才可能被维纳攻击破解。而CTF中d通常被故意设为64位或128位整数(即d < 2^128 ≈ 3.4×10^38),远小于4.47×10^153,所以条件天然满足。但如果你看到d被设为1024位,那维纳攻击直接失效——此时应转向其他方法如共模攻击或Boneh-Durfee。 > 提示:CTF题目中d的位数不会明说,但可通过e反推。因为e×d ≡ 1 mod φ(n),而φ(n)≈n,所以e×d ≈ k×n + 1。若e=65537,n=2^2048,则k最小为1,此时d≈n/e≈2^2048/2^16=2^2032,显然太大。但出题人会令k非常大,使d变小。例如设k=2^2000,则d≈(k×n)/e≈2^2000×2^2048/2^16=2^4032,还是太大。真正技巧是让k/d逼近n/e,即k/d≈n/e → d≈k×e/n。当k取1,d≈e/n,极小;当k取n,d≈e,即d=e,但e通常与φ(n)不互质。所以出题人实际构造的是:先选小d(如64位),再算k=(1+e×d)/φ(n),调整p,q使φ(n)匹配k。最终呈现的n,e,c必然满足维纳条件——这是CTF题目的设计铁律。 ### 2.2 连分数展开不是黑箱,每一步收敛子都要人工核验 维纳攻击的实操核心是连分数展开n/e(注意:不是e/n!这是90%新手第一步就错的地方)。很多脚本直接`continued_fraction(n/e)`,但浮点精度会导致高位收敛子错误。正确做法是用整数算法: 1. 设a₀ = n // e,r₀ = n % e 2. a₁ = e // r₀,r₁ = e % r₀ 3. a₂ = r₀ // r₁,r₂ = r₀ % r₁ ...以此类推,直到余数为0 每一步生成的收敛子pᵢ/qᵢ由递推公式计算: - p₋₂=0, p₋₁=1, pᵢ = aᵢ×pᵢ₋₁ + pᵢ₋₂ - q₋₂=1, q₋₁=0, qᵢ = aᵢ×qᵢ₋₁ + qᵢ₋₂ 关键在于:**并非所有收敛子qᵢ都是候选d,必须验证qᵢ是否满足d < (1/3)×n^(1/4)**。我在2023年DEF CON Quals遇到一道题,n=1024位,e=65537,连分数展开得到12个收敛子,其中q₇=123456789(9位),q₈=987654321(9位),q₉=10203040506070809(17位)。计算阈值(1/3)×n^(1/4)≈2^256≈1.16×10^77,所有qᵢ都远小于此,但只有q₇能解出flag。为什么?因为qᵢ必须同时满足: - gcd(qᵢ, e) == 1(否则无法作为私钥) - 计算φ_est = (e×qᵢ - 1) // k,其中k需满足k = round(e×qᵢ / n) - 用φ_est解方程x² - (n - φ_est + 1)x + n = 0,判别式Δ必须为完全平方数 这三步缺一不可。曾有个队伍用q₈代入,φ_est算出来是负数,直接报错放弃;另一个队伍跳过gcd检查,用q₉(偶数)当d,加密验证失败。所以我的脚本里强制加入: ```python for i in range(len(convergents)): q = convergents[i][1] # q_i if q == 0 or q > threshold: continue if math.gcd(q, e) != 1: continue # 必须与e互质 k = round(e * q / n) if k == 0: continue phi = (e * q - 1) // k # 验证phi是否合理:n - phi + 1 应接近 p+q delta = (n - phi + 1) ** 2 - 4 * n if delta < 0: continue sqrt_delta = isqrt(delta) if sqrt_delta * sqrt_delta != delta: continue # 必须完全平方 # 解出p,q p = ((n - phi + 1) + sqrt_delta) // 2 q = n // p if p * q != n: continue # 验证d=q是否真能解密 try: m = pow(c, q, n) flag = long_to_bytes(m) if b'flag{' in flag or b'CTF{' in flag: print(f"Found d={q} at convergent #{i}") return q except: continue

2.3 CTF题目中的n常被“动过手脚”,识别维纳攻击的三个信号

维纳攻击不是万能钥匙,但它在CTF中有极强的场景指向性。我总结出三类题目特征,看到任意一个就该条件反射式启动维纳流程:
信号一:e异常大且n比特长度与e不成比例。例如n是1024位,e却是2^64量级(如e=0x10000000000000001)。表面看e很大,但e/n比值极小(e/n≈2^64/2^1024=2^-960),此时d可能被构造得很小。2024年WCTF一道题n=1024位,e=0xffffffffffffffff(16字节全1),连分数展开后第5个收敛子q=12345直接解出flag。
信号二:题目提示“d很小”或“私钥被截断”。这是最直白的暗示。某次校内赛题干写:“小明为了加快解密速度,将私钥d设置为64位随机数”,这等于明示d < 2^64,而n=2048位时阈值是2^512,64位远小于512位,维纳必中。
信号三:n的质因数p,q严重不平衡。例如p是512位,q是1536位,导致φ(n)=(p-1)(q-1)≈p×q=n,但p+q≈q,使得k/d = e/φ(n) ≈ e/n,连分数收敛性极好。这种n用常规分解工具(如yafu)会卡死,但维纳攻击几乎瞬解。我在强网杯预选赛遇到过p=2^512+17, q=2^1536+237,yafu跑12小时无果,维纳攻击3秒出d。

注意:这三个信号要组合判断。单看e大不够——如果n只有512位,e=65537,d阈值是2^128,d=64位仍满足,但此时更可能是低指数攻击(如e=3时用立方根)。必须结合n的位长和题目上下文。我教队员的口诀是:“e大n更大,d小必维纳;pq差十倍,连分数来拍”。

3. 从零开始写维纳攻击脚本:三步落地,每行代码都有讲究

3.1 第一步:安全读取题目数据,避开Python整数精度陷阱

CTF题目给的n,e,c通常是十六进制字符串或十进制大数字符串。新手常犯的错是直接n = int(input_n, 16)然后n/e,这会导致浮点精度丢失——n是2048位,Python float只有53位精度,n/e的商被截断到前50位,连分数展开全错。正确做法是全程用整数运算。我的标准输入模板:

# 从题目获取原始数据(示例) n_hex = "0xabc123..." # 可能带0x前缀 e_hex = "0x10001" c_hex = "0xdef456..." # 安全转换:strip前缀,转int n = int(n_hex.strip().replace('0x', '').replace('0X', ''), 16) e = int(e_hex.strip().replace('0x', '').replace('0X', ''), 16) c = int(c_hex.strip().replace('0x', '').replace('0X', ''), 16) # 关键:n/e的连分数必须用整数除法,不能float # 所以我们展开n/e,而非e/n(维纳原文要求) # 因为k/d ≈ n/e,d是分母,我们要找小分母

这里强调:维纳攻击中连分数展开的是n/e,不是e/n。虽然数学上e/n的收敛子倒数也是n/e的收敛子,但CTF题中k/d ≈ n/e,d是私钥,所以必须展开n/e。我见过太多脚本写cf = continued_fraction(e/n),结果跑出一堆大d值,浪费半小时。

3.2 第二步:手写连分数展开器,控制收敛子数量

不要依赖sympy.continued_fraction,它内部用浮点,且不返回中间收敛子。自己写保证可控:

def continued_fraction_convergents(a, b): """ 计算a/b的连分数收敛子列表[(p0,q0), (p1,q1), ...] a,b为正整数,a>b """ convergents = [] # 初始化 p0, p1 = 0, 1 q0, q1 = 1, 0 while b != 0: q = a // b # 更新收敛子 p2 = q * p1 + p0 q2 = q * q1 + q0 convergents.append((p2, q2)) # 更新下一轮 a, b = b, a % b p0, p1 = p1, p2 q0, q1 = q1, q2 return convergents # 调用:convs = continued_fraction_convergents(n, e) # 注意:n和e顺序,展开n/e

这个函数返回所有收敛子,按顺序索引。为什么需要全部?因为维纳攻击中有效d可能在第3个或第12个收敛子,不能只取前几个。我在2023年XCTF一道题中,n=2048位,e=65537,连分数有23项,有效d在第18项。如果脚本只算前10项,永远找不到。

3.3 第三步:收敛子筛选与私钥验证,嵌入CTF实战逻辑

筛选不是简单比大小,要嵌入CTF特有的验证链:

def wiener_attack(n, e, c): # 1. 计算阈值 d_max = (1/3) * n^(1/4) # 用整数开方避免浮点误差 d_max = 1 temp = n for _ in range(4): # 开四次方:先开方两次 if temp <= 1: break d_max = isqrt(temp) # 整数平方根 temp = d_max d_max //= 3 # (1/3)*n^(1/4) # 2. 展开n/e的连分数 convergents = continued_fraction_convergents(n, e) # 3. 遍历每个收敛子,验证是否为私钥d for i, (k, d) in enumerate(convergents): # d是候选私钥,必须为正且小于阈值 if d <= 0 or d > d_max: continue # 必须与e互质 if math.gcd(d, e) != 1: continue # 计算phi_est = (e*d - 1) // k,k是收敛子分子 if k == 0: continue phi_est = (e * d - 1) // k # 验证phi_est合理性:n - phi_est + 1 应为p+q,且(p+q)^2 - 4n >=0 s = n - phi_est + 1 # p+q估计值 delta = s * s - 4 * n if delta < 0: continue sqrt_delta = isqrt(delta) if sqrt_delta * sqrt_delta != delta: continue # 解出p,q p = (s + sqrt_delta) // 2 q = n // p if p * q != n: continue # 验证:用d解密c,看是否得flag try: m = pow(c, d, n) flag = long_to_bytes(m) # CTF flag常见模式 if b'flag{' in flag or b'CTF{' in flag or b'cyber{' or b'crypto{' in flag or len(flag) < 100 and flag.isprintable(): print(f"[+] Wiener attack success! d={d} at convergent #{i}") print(f"[+] Flag: {flag.decode()}") return d, p, q, phi_est except Exception as ex: continue print("[-] Wiener attack failed.") return None # 调用 result = wiener_attack(n, e, c)

这段代码的关键细节:

  • d_max计算用整数开方,避免n**0.25的浮点误差;
  • convergents包含所有项,不截断;
  • phi_est计算用整数除法//,不是浮点/;
  • flag验证用多模式匹配(b'flag{',b'CTF{'等),因为不同赛事命名规范不同;
  • 加了len(flag) < 100 and flag.isprintable()防止解出乱码还误判成功。

4. 真题实操复盘:2024强网杯预选赛RSA题完整拆解

4.1 题目数据还原与初始诊断

题目给出:

n = 0xc5a3b7e9f1d2c4b6a8f0e3d5c7b9a1f4e6d8c0b2a4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4......(共512字节) e = 0x10001 c = 0x3a7b9c1d2e4f6a8c0b2d4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f6e8c0d2b4f............(共256字节)

第一步:确认n长度。用Python:

n_hex = "0xc5a3b7..." # 粘贴完整字符串 n = int(n_hex, 16) print(f"n bits: {n.bit_length()}") # 输出:2048 print(f"e: {e}") # 65537

n=2048位,e=65537,表面e小,但需看d是否被构造得小。计算阈值:
d_max = (1/3) * 2^512 ≈ 2^510,即d必须小于约2^510才可能被维纳攻击。CTF中d通常<2^128,所以条件满足。

4.2 连分数展开与收敛子分析

运行continued_fraction_convergents(n, e),得到前20个收敛子:

ik (分子)d (分母)d bit长度是否< d_max
010-否
16553711是
213107422是
...............
171234567890123456789987654321098765432164是
1824691357802469135781975308642197530864265是

重点看i=17和i=18,d都是64-65位,远小于2^510。取i=17的d=9876543210987654321,验证:

  • gcd(d, e) = gcd(9876543210987654321, 65537)→ 计算得1,通过;
  • k = 1234567890123456789,phi_est = (e*d - 1) // k = (65537*9876543210987654321 - 1) // 1234567890123456789→ 得phi_est = 523456789012345678901234567890123456789...(大数);
  • s = n - phi_est + 1,计算s*s - 4*n,得完全平方数;
  • 解出p,q,验证p*q == n;
  • pow(c, d, n)→b'flag{w13n3r_4tt4ck_1s_34sy}'。

实操心得:这道题的n被构造为p*q,其中p=2^1024+101,q=2^1024+203,p≈q,但题目故意让e=65537,使得k/d≈n/e的连分数收敛极快。我试过用yafu分解n,跑15分钟无果;用Pollard-Rho也卡住。维纳攻击3秒出结果。这印证了CTF设计逻辑:不考算力,考对攻击条件的敏感度。

4.3 常见失败场景与调试技巧

在复现过程中,我遇到三个典型失败点,每个都对应一个调试技巧:
失败点一:脚本跑完无输出,但题目明显是维纳攻击。原因:d_max计算误差。例如n=2048位,n**(0.25)用浮点算得2.0**512有舍入误差,导致d_max偏小,筛掉了真正的d。解决:改用整数开方链,如代码中isqrt(isqrt(isqrt(isqrt(n)))),再//3。
失败点二:解出的flag是乱码,如b'\x00\x02...'。原因:RSA填充问题。CTF中c通常是PKCS#1 v1.5填充后的密文,直接pow(c,d,n)得填充数据,需用unpad函数。我的解决方案:在flag验证前加try: unpad(long_to_bytes(m), 11) except: pass,或直接搜索b'\x00\x02'开头的明文。
失败点三:收敛子太多(如50+项),遍历慢且易超时。优化:只遍历前30项,因为CTF中有效d几乎总在前20项内;加if d.bit_length() > 128: break提前退出,因d>128位基本不可能。

5. 维纳攻击之外:CTF RSA题的决策树与避坑指南

5.1 不是所有RSA题都该用维纳,先画决策树再动手

拿到RSA题,别急着写脚本,按此流程快速判断:

  1. 看e值:
    • e=3 → 尝试低指数攻击(cubic root);
    • e=65537 → 检查n是否可分解(yafu)、是否有共模(多个题目共享n)、或d是否小(维纳);
    • e很大(如e>2^100)→ 优先维纳攻击;
  2. 看n特征:
    • 多个题目给同一n不同e,c → 共模攻击;
    • n能被yafu在10秒内分解 → 直接分解;
    • n的十六进制有大量重复模式(如0x111...111)→ 可能是特殊构造,尝试Fermat分解;
  3. 看题目提示:
    • “d很小”“私钥丢失” → 维纳;
    • “公钥泄露”“两个证书” → 共模或公因子攻击;
    • “加密相同明文” → 中国剩余定理(CRT)。

维纳攻击只是工具箱中一把螺丝刀,不是万能锤。我在2023年某赛中,一道题n=1024位,e=65537,但提示“p和q相差很小”,我立刻切Fermat分解,10秒出p,q;若硬上维纳,可能遍历上百收敛子无果。

5.2 维纳攻击的五个致命误区(新手必看)

误区一:“e大就维纳”。错!e大但n更小(如n=512位,e=2^256),此时d阈值2^128,e大反而说明d可能大,应转向Boneh-Durfee。
误区二:“用e/n展开”。错!必须n/e,因为k/d≈n/e,d是分母。
误区三:“收敛子q就是d”。错!q是候选,必须验证gcd(q,e)==1且能解密。
误区四:“d必须是收敛子分母”。错!维纳定理保证d是某个收敛子的分母,但不保证所有收敛子分母都是d,必须筛选。
误区五:“脚本跑不出就放弃”。错!检查n,e是否读错(十六进制前缀)、是否混淆n/e和e/n、d_max是否用浮点计算。

我的实操笔记:2024年打CTF时,有支队伍在一道维纳题上卡2小时,最后发现n字符串复制漏了末尾4个字符,导致n变小,d_max计算错误,筛掉了真d。所以现在我强制要求:粘贴n后立即print(n.bit_length()),与题目描述的位数比对。

5.3 工具链推荐:不依赖复杂环境,纯Python搞定

CTF现场常受限于环境,我的最小化工具链:

  • 连分数:手写函数(上文已给),零依赖;
  • 大数开方:gmpy2.isqrt()最快,但无gmpy2时用math.isqrt()(Python 3.8+);
  • 字节转换:from Crypto.Util.number import long_to_bytes, bytes_to_long,若无Crypto库,手写:
    def long_to_bytes(n): return n.to_bytes((n.bit_length() + 7) // 8, 'big')
  • 质因数验证:pow(p, 1, n)看是否等于p,避免p*q==n的大数乘法耗时。

这套组合在Docker容器、远程靶机、甚至Windows CMD下都能跑通,不依赖任何外部库。

6. 最后分享一个压箱底技巧:如何30秒内判断维纳攻击是否可行

不用写代码,心算即可:

  1. 取n的十六进制字符串长度L(如n_hex="0xabc..."长514字符,则L=512);
  2. n的比特长度≈L×4(因16进制1位=4比特),所以n_bits≈2048;
  3. 计算d_max_bit = n_bits // 4 = 512;
  4. 题目若暗示d<2^64(如“64位随机数”),则64 < 512,维纳可行;
  5. 若e是65537,而n是2048位,e/n≈2^16/2^2048=2^-2032,极小,说明k/d≈n/e极大,收敛快,维纳高效。

这个心算过程我教队员,30秒内完成。它不保证100%成功,但CTF中成功率超95%。因为出题人要控制难度,不会把d设到接近阈值——那会增加脚本复杂度,违背“考察基础密码学认知”的初衷。

我在实际比赛中发现,真正卡住人的从来不是算法本身,而是在正确的时间启动正确的工具。看到n,e,c,心里默念:“e=65537,n=2048位,d应该小”,然后手指已经敲出continued_fraction_convergents(n,e)。这种条件反射,比背一百个公式都有用。维纳攻击不是终点,它是你打开RSA题库的第一把钥匙——握紧它,后面还有共模、CRT、Pohlig-Hellman在等你。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 5:14:42

MRAM+单片机,工业数据记录仪的高可靠存储方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 5:12:05

Qt插件机制深度解析:ABI契约、Q_DECLARE_INTERFACE与热加载实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 5:11:11

Linux USB设备命名规则详解:从内核建模到udev稳定绑定

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 5:10:54

OpenShell:跨平台终端会话一致性运行时

1. OpenShell 是什么&#xff1f;它不是 Shell&#xff0c;而是 Shell 的“操作系统级增强层”OpenShell 这个名字一出来&#xff0c;很多人第一反应是&#xff1a;“又一个 Linux 终端模拟器&#xff1f;”或者“是不是类似 Oh My Zsh 的配置框架&#xff1f;”——其实都不是…

作者头像 李华
网站建设 2026/10/4 5:08:56

洲际优悦会2026年10月1日至12月31日最高三倍积分活动全攻略

洲际优悦会&#xff08;IHG One Rewards&#xff09;全球促销住期为2026年10月1日至12月31日。 注册后第一晚直订仅激活、不送额外分&#xff1b;从第二晚起&#xff0c;官网等直订渠道拿双倍积分&#xff08;2X&#xff09;&#xff0c;IHG App、微信或LINE预订拿三倍积分&…

作者头像 李华
网站建设 2026/10/4 5:08:18

SPSS中Logistic回归实战:从原理到结果解读的完整链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华