The Thirteen Seals of RSA:13 关 RSA 弱点串烧

这题的设计我非常喜欢:13 关,每关一个经典 RSA 弱点,各加密 flag 的一段,按顺序拼接就是完整 flag。

段长:11×3 字节 + 1×5 字节 + 1×7 字节 = 45 字节
FLAG[PART_STARTS[i-1] : PART_STARTS[i-1]+PART_LENS[i-1]]

基本等于把 CryptoHack 的 RSA 章节串成一条链,做一遍就是一次系统复习。

关卡速查表

关段弱点手法
01TSCm^11 < n直接整数开 11 次根
02TF-e=3,只溢出一点点穷举 k<1000:m³ = c + kn
03J{HRabin,p,q ≡ 3 (mod 4)模平方根,4 个根 + 约束筛选
04appd 只有 64 bitWiener 连分数攻击
05y_R同明文、e=3、三组公钥Håstad 广播:CRT 后开立方
06SA_p−1 是 1000-光滑Pollard p−1
07Leap+1 光滑Williams p+1(Lucas 序列)
08rni同一个 n,e1=65537, e2=17共模攻击(扩展欧几里得)
09ng_dp, dq 泄露CRT 合并两次解密
10witdp 泄露,e=257 很小穷举 k < e
11h_Cdp 泄露,e 是 128 bitgcd(a^(e·dp) − a, n)
12rypto已知 e, ded−1 = 2^t·r 分解 n
13hack!!}已知 p ⊕ q从低位逐位剪枝

几个值得记的点

整数开根不能偷懒用浮点

第 1 关 m^11 < n,直接用 int(c ** (1/11)) 会因为浮点精度在多字节整数上丢位。必须写整数牛顿迭代:

def iroot(n, k):
    """整数 k 次根(向下取整)"""
    x = 1 << ((n.bit_length() + k - 1) // k)
    while True:
        y = ((k - 1) * x + n // x ** (k - 1)) // k
        if y >= x:
            break
        x = y
    while x ** k > n:
        x -= 1
    while (x + 1) ** k <= n:
        x += 1
    return x

最后那两行”微调”很关键——牛顿迭代收敛到的是近似值,大整数下差 1 就全错。

第 13 关:已知 p ⊕ q 的逐位剪枝

这关是整题最难的。已知 n 和 p ^ q(异或),要求分解 n。

思路是从最低位往高位逐位确定 p 和 q:

# 已知低 i 位,试 p_bit ∈ {0,1},q_bit = p_bit ^ xor_bit
# 剪枝条件:低位乘积必须等于 n 的低位
def factor_from_xor(n, pxorq, bits):
    cands = [(0, 0)]
    for i in range(bits):
        nxt = []
        xb = (pxorq >> i) & 1
        for p, q in cands:
            for pb in (0, 1):
                qb = pb ^ xb
                np_ = p | (pb << i)
                nq_ = q | (qb << i)
                # 低 i+1 位乘积必须匹配 n 的低 i+1 位
                if (np_ * nq_) & ((1 << (i + 1)) - 1) == n & ((1 << (i + 1)) - 1):
                    nxt.append((np_, nq_))
        cands = nxt
    return cands

关键在剪枝条件:如果 p 和 q 的低 i+1 位正确,那么 p·q 的低 i+1 位必然等于 n 的低 i+1 位。这一条把搜索空间死死压住,每一步候选数都很少。

Håstad 广播为什么有效

第 5 关同一明文用 3 组互质模数、都是 e=3 加密。CRT 合并后得到 m³ mod (n1·n2·n3),而 m³ < n1·n2·n3,所以那个同余式就是真正的 m³,直接开立方即可。

这就是”广播攻击”的本质:不是密码学被破,而是信息量给多了。

踩坑

问题原因解决
开根结果差 1浮点精度丢失整数牛顿迭代 + 后置微调
Rabin 出 4 个根数学上本来就有 4 个用”结果必须是合法 ASCII”筛选
Wiener 攻击误判连分数收敛需要判据disc = (n−φ+1)² − 4n 必须是完全平方
Pollard p−1 卡住界 B 给小了逐步加 B(1000 → 5000 → 20000)

小结

做完这题最大的收获不是记住 13 种攻击,而是形成了一个判断流程:

  1. e 小不小?(1/2/3/5/17/65537)
  2. m 大不大?m^e 有没有超过 n?
  3. 有没有额外泄露?(dp、dq、d、p⊕q、φ(n))
  4. 有没有多组密文?(同模 → 共模;不同模同明文 → 广播)
  5. n 本身有没有规律?(p−1/p+1 光滑 → Pollard/Williams)

按这个顺序走一遍,绝大多数 RSA 题都能定性到具体攻击。