CTF密码学综合教学指南--第五章

第五章:RSA非对称加密

5.1 RSA算法原理

RSA是最经典的非对称加密算法,其安全性基于大整数分解的困难性。

密钥生成过程:

  1. 随机选取两个大素数 pq
  2. 计算 n = p × q(模数)
  3. 计算 φ(n) = (p-1)(q-1)(欧拉函数值)
  4. 选取公钥指数 e,满足 1 < e < φ(n) 且 GCD(e, φ(n)) = 1(通常取65537)
  5. 计算私钥指数 d = e⁻¹ mod φ(n)(e的模逆元)

公钥: (n, e) 私钥: (n, d)

加密: c = me mod n

解密: m = cd mod n

复制代码
from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes

# RSA密钥生成与加解密示例
p = getPrime(512) # 512位素数
q = getPrime(512)
n = p * q
phi = (p - 1) * (q - 1)
e = 65537
d = pow(e, -1, phi) # Python 3.8+ 支持

print(f"p = {p}")
print(f"q = {q}")
print(f"n = {n}")
print(f"e = {e}")
print(f"d = {d}")

# 加密
message = b"flag{rsa_basic_example}"
m = bytes_to_long(message)
c = pow(m, e, n)
print(f"\n密文 c = {c}")

# 解密
m_decrypted = pow(c, d, n)
plaintext = long_to_bytes(m_decrypted)
print(f"解密结果: {plaintext}")

5.2 RSA常见攻击方法

5.2.1 已知p, q直接解密

最基础的情况:题目直接给出n, e, c以及p和q(或通过其他方式可以获得p和q)。

复制代码
from Crypto.Util.number import long_to_bytes

# 已知参数
p = 473398607161
q = 4511491
e = 17
n = p * q
c = 2135733555619 # 密文

# 计算私钥d
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

# 解密
m = pow(c, d, n)
plaintext = long_to_bytes(m)
print(f"明文: {plaintext}")

5.2.2 小公钥指数攻击(Low Public Exponent Attack)

适用条件: e 很小(如e=3),且明文m也较小,使得 m^e < n。

原理: 此时 c = m^e mod n 实际上等于 c = m^e(没有发生模运算),直接对c开e次方根即可。

复制代码
import gmpy2
from Crypto.Util.number import long_to_bytes

# 题目给出的参数
n = 0x00b0bee5e3e9e5a7e8d00b493355c618fc8c7d7d03b82e409951c182f398dee3104580e7ba70d383ae5311475656e8a964d380cb157f48c951adfa65db0b122ca40e42fa709189b719a4f0d746e2f6069baf11cebd650f14b93c977352fd13b1eea6d6e1da775502abff89d3a8b3615fd0db49b88a976bc20568489284e181f6f11e270891c8ef80017bad238e363039a458470f1749101bc29949d3a4f4038d463938851579c7525a69a8a3ec7e066637687f5126d32ffcfb6e0fef02c986c0c85f2ae0ee1eaec6735b4db6ce7a7db040a0886b12fb4af7bac7ebcf1d35a4f63c413f2aab82d0c94915e9a7fd13bc62bb1da3e35aebf3a4a3b1b138ac9876b3fdd3e3f3
e = 3
c = 0x32c7cae5c6cf22e5ee1a7a3d9e93bc9c4ce6d942deb5b8bbff771e9bd32e3c3e6ab1d84d3f28bae9726e6d28bb3e2c26

# 直接对c开立方根
m, exact = gmpy2.iroot(c, e)
if exact:
    print(f"攻击成功!")
    print(f"明文: {long_to_bytes(int(m))}")
else:
    # 如果不精确,可能需要加上k*n再开方
    for k in range(10000):
        m, exact = gmpy2.iroot(c + k * n, e)
        if exact:
            print(f"k={k}, 明文: {long_to_bytes(int(m))}")
            break

5.2.3 共模攻击(Common Modulus Attack)

适用场景: 同一明文m使用相同的n但不同的公钥e1、e2加密,产生密文c1、c2。

条件: GCD(e1, e2) = 1

原理: 由扩展欧几里得算法找到s1, s2使得 e1×s1 + e2×s2 = 1,则:

m = c1s1 × c2s2 mod n

复制代码
from Crypto.Util.number import long_to_bytes
from math import gcd

def common_modulus_attack(n, e1, e2, c1, c2):
    """共模攻击"""
    assert gcd(e1, e2) == 1, "e1和e2必须互素"
    
    # 扩展欧几里得算法
    def extended_gcd(a, b):
        if a == 0:
            return b, 0, 1
        g, x, y = extended_gcd(b % a, a)
        return g, y - (b // a) * x, x
    
    _, s1, s2 = extended_gcd(e1, e2)
    
    # 处理负指数:c^(-s) = (c^(-1))^s mod n
    if s1 < 0:
        c1 = pow(c1, -1, n)
        s1 = -s1
    if s2 < 0:
        c2 = pow(c2, -1, n)
        s2 = -s2
    
    # 计算明文
    m = (pow(c1, s1, n) * pow(c2, s2, n)) % n
    return m

# 示例
n = 23562080109018038742778820483568982347827540520135171070210
e1 = 17
e2 = 65537
c1 = 10827832305829964204060891824808894890388408390698982394856
c2 = 15551873521651498272047612592927934446456498781204104392073

m = common_modulus_attack(n, e1, e2, c1, c2)
print(f"明文: {long_to_bytes(m)}")

5.2.4 Wiener攻击(d较小时)

适用条件: 私钥d < n^(1/4) / 3

原理: 当d较小时,e/n的连分数展开中的某个收敛子(convergent)的分母就是d。

复制代码
def wiener_attack(e, n):
    """Wiener攻击:利用连分数逼近恢复私钥d"""
    from Crypto.Util.number import long_to_bytes
    
    def continued_fraction(num, den):
        """计算连分数展开"""
        cf = []
        while den:
            q = num // den
            cf.append(q)
            num, den = den, num - q * den
        return cf
    
    def convergents(cf):
        """计算连分数的所有收敛子"""
        convs = []
        h_prev, h_curr = 0, 1
        k_prev, k_curr = 1, 0
        for a in cf:
            h_prev, h_curr = h_curr, a * h_curr + h_prev
            k_prev, k_curr = k_curr, a * k_curr + k_prev
            convs.append((h_curr, k_curr))
        return convs
    
    cf = continued_fraction(e, n)
    for k, d in convergents(cf):
        if k == 0:
            continue
        # 检验:如果(e*d - 1)能被k整除
        if (e * d - 1) % k != 0:
            continue
        phi = (e * d - 1) // k
        # phi(n) = n - p - q + 1,所以 p + q = n - phi + 1
        s = n - phi + 1
        # p和q是 x^2 - s*x + n = 0 的根
        discriminant = s * s - 4 * n
        if discriminant < 0:
            continue
        from gmpy2 import isqrt, is_square
        if is_square(discriminant):
            sqrt_d = isqrt(discriminant)
            p = (s + sqrt_d) // 2
            q = (s - sqrt_d) // 2
            if p * q == n:
                return d, p, q
    return None

# 使用示例
# result = wiener_attack(e, n)
# if result:
#     d, p, q = result
#     m = pow(c, d, n)
#     print(long_to_bytes(m))

5.2.5 Fermat分解法(p和q接近时)

原理: 当p和q非常接近时(|p-q|较小),n可以用Fermat方法快速分解。

令 a = ⌈√n⌉,计算 b² = a² - n,如果b²是完全平方数,则 p = a + b, q = a - b。

复制代码
import gmpy2

def fermat_factor(n):
    """Fermat分解法:适用于p和q接近的情况"""
    a = gmpy2.isqrt(n)
    if a * a == n:
        return int(a), int(a)
    a += 1 # 从ceil(sqrt(n))开始
    
    while True:
        b_squared = a * a - n
        if gmpy2.is_square(b_squared):
            b = gmpy2.isqrt(b_squared)
            p = int(a + b)
            q = int(a - b)
            return p, q
        a += 1
        
        # 安全限制:避免无限循环
        if a - gmpy2.isqrt(n) > 1000000:
            return None, None

# 示例
n = 1000000007 * 1000000009 # p和q非常接近
p, q = fermat_factor(n)
print(f"p = {p}")
print(f"q = {q}")
print(f"验证: p*q == n? {p*q == n}")

5.2.6 Pollard p-1分解法

适用条件: p-1是光滑数(smooth number),即p-1的所有素因子都不超过某个较小的界B。

算法原理: 如果p-1的所有素因子的幂次都不超过B!,那么 (p-1) | B!。由费马小定理,a^(B!) ≡ 1 (mod p),于是 GCD(a^(B!) - 1, n) 可能给出p。

复制代码
from math import gcd, log

def pollard_p1(n, B=100000):
    """Pollard p-1分解法"""
    a = 2
    for j in range(2, B + 1):
        a = pow(a, j, n)
    d = gcd(a - 1, n)
    if 1 < d < n:
        return d, n // d
    return None, None

# 示例:p-1是光滑数的情况
# p, q = pollard_p1(n)
# print(f"分解结果: p={p}, q={q}")

5.2.7 Håstad广播攻击

适用场景: 相同的明文m使用相同的小e(如e=3)但不同的模数n1, n2, ..., ne加密后发送给多人。

原理: 利用中国剩余定理(CRT),从e组密文中恢复m^e mod (n1×n2×...×ne),由于m^e < n1×n2×...×ne,直接开e次方根即得m。

复制代码
import gmpy2
from Crypto.Util.number import long_to_bytes
from functools import reduce

def chinese_remainder_theorem(remainders, moduli):
    """中国剩余定理"""
    N = reduce(lambda a, b: a * b, moduli)
    result = 0
    for r, n in zip(remainders, moduli):
        Ni = N // n
        yi = pow(Ni, -1, n)
        result = (result + r * Ni * yi) % N
    return result

def hastad_attack(ciphertexts, moduli, e):
    """Hastad广播攻击"""
    # 使用CRT合并
    combined = chinese_remainder_theorem(ciphertexts, moduli)
    # 开e次方根
    m, exact = gmpy2.iroot(combined, e)
    if exact:
        return int(m)
    return None

# 示例:e=3,三组密文
# m = hastad_attack([c1, c2, c3], [n1, n2, n3], 3)
# print(long_to_bytes(m))

5.2.8 Coppersmith攻击简介

适用场景: 已知明文的部分高位或低位信息,利用格基约化(LLL算法)恢复完整明文。

典型 CTF 应用:

  • 已知flag的前缀(如"flag{"),padding后加密
  • RSA中消息的高位已知(Stereotyped Message Attack)
  • RSA中p的部分比特已知(Partial Key Exposure)

SageMath 实现:

复制代码
# SageMath代码(在SageMath环境中运行)
# 场景:已知明文高位,恢复低位
# n, e, c = ...(题目给出)
# known_high_bits = ...(已知的高位信息)

# P.<x> = PolynomialRing(Zmod(n))
# f = (known_high_bits + x)^e - c
# roots = f.small_roots(X=2^64, beta=1.0)

# if roots:
#     m = known_high_bits + int(roots[0])
#     print(long_to_bytes(m))

5.2.9 factordb.com查询

factordb.com是一个在线大整数分解数据库。许多CTF题目中的n可能已经被收录。

复制代码
# pip install factordb-python
from factordb.factordb import FactorDB

def factordb_query(n):
    """在factordb中查询n的分解"""
    f = FactorDB(n)
    f.connect()
    status = f.get_status() # "FF"表示完全分解
    factors = f.get_factor_list()
    print(f"状态: {status}")
    print(f"因子: {factors}")
    return factors

# 示例
# factors = factordb_query(n)
# if len(factors) == 2:
#     p, q = factors
#     # 继续RSA解密...

5.3 RSA综合实战案例

案例1:基础RSA解密

|----------------------------------------|
| 📋 题目 已知RSA参数如下,请解密密文c,恢复flag。 |

复制代码
from Crypto.Util.number import long_to_bytes

# 题目给出的参数
p = 285960468890451637935629440372639283459
q = 304535446137785155622451010547307234543
e = 65537
c = 52584702924206682637115973187691622109377186446486767779190105859467455678109

# 第一步:计算n
n = p * q
print(f"n = {n}")

# 第二步:计算欧拉函数φ(n)
phi = (p - 1) * (q - 1)
print(f"φ(n) = {phi}")

# 第三步:计算私钥d
d = pow(e, -1, phi)
print(f"d = {d}")

# 第四步:解密
m = pow(c, d, n)
print(f"m = {m}")

# 第五步:转换为字节串
flag = long_to_bytes(m)
print(f"flag = {flag}")

案例2:小指数攻击实战

|------------------------------------------------------------|
| 📋 题目 n = (一个很大的数), e = 3, c = (一个相对较小的数)。提示:明文较短。 |

复制代码
import gmpy2
from Crypto.Util.number import long_to_bytes

# 分析:e=3,明文短 → m^3 可能小于n → 直接开立方根
n = 0xBDAECA1B370A7D2DD7A6F4B14E2C14E53C034CBAB7CC3
e = 3
c = 0x6D94F7E0BE6FE7B0F3BF0A7A # 密文远小于n

# 尝试直接开立方根
m, exact = gmpy2.iroot(c, 3)
if exact:
    print(f"直接开方成功!")
    print(f"m = {m}")
    print(f"flag = {long_to_bytes(int(m))}")
else:
    print("直接开方不精确,尝试加k*n...")
    for k in range(100000):
        m, exact = gmpy2.iroot(c + k * n, 3)
        if exact:
            flag = long_to_bytes(int(m))
            if b'flag' in flag or b'ctf' in flag:
                print(f"k={k}, flag = {flag}")
                break

案例3:共模攻击实战

|----------------------------------------------------------------------------------------------------------|
| 📋 题目 Alice将同一条消息用两个不同的公钥 (n, e1) 和 (n, e2) 加密后分别发送。你截获了两份密文c1和c2。已知:n, e1=17, e2=65537, c1, c2。 |

复制代码
from Crypto.Util.number import long_to_bytes
from math import gcd

# 题目参数
n = 22708078815885011462462049064339185898712439277226831073457888403129
e1 = 17
e2 = 65537
c1 = 19131871765043780989007785498026791271873838245298258088153517748283
c2 = 20912698415719344905648896668429947622856831909672000736438709258961

# 确认e1, e2互素
assert gcd(e1, e2) == 1, "e1和e2必须互素"

# 扩展欧几里得算法
def extended_gcd(a, b):
    if a == 0:
        return b, 0, 1
    g, x, y = extended_gcd(b % a, a)
    return g, y - (b // a) * x, x

_, s1, s2 = extended_gcd(e1, e2)
print(f"s1 = {s1}, s2 = {s2}")
print(f"验证: e1*s1 + e2*s2 = {e1*s1 + e2*s2}") # 应该等于1

# 处理负指数
if s1 < 0:
    c1_use = pow(c1, -1, n) # 计算c1的模逆元
    s1 = -s1
else:
    c1_use = c1

if s2 < 0:
    c2_use = pow(c2, -1, n)
    s2 = -s2
else:
    c2_use = c2

# 恢复明文: m = c1^s1 * c2^s2 mod n
m = (pow(c1_use, s1, n) * pow(c2_use, s2, n)) % n
flag = long_to_bytes(m)
print(f"恢复的明文: {flag}")
相关推荐
callJJ2 小时前
Spring Data Redis 两种编程模型详解:同步 vs 响应式
java·spring boot·redis·python·spring
小郑加油2 小时前
python学习Day12:pandas安装与实际运用
开发语言·python·学习
AC赳赳老秦2 小时前
投标合规提效:用 OpenClaw 实现标书 / 合同自动审核、关键词校验、格式优化,降低废标风险
开发语言·前端·python·eclipse·emacs·deepseek·openclaw
.柒宇.2 小时前
AI掘金头条项目-K8s部署实战教程
python·云原生·容器·kubernetes·fastapi
S1998_1997111609•X3 小时前
论mysql国盾shell-sfa犯罪行为集团下的分项工程及反向注入原理尐深度纳米算法下的鐌檵鄐鉎行为
网络·数据库·网络协议·百度·开闭原则
KuaCpp3 小时前
C++面向对象(速过复习版)
开发语言·c++
观北海3 小时前
从 Sim2Sim 到 Sim2Real:以 ONNX 为核心的机器人策略实机落地全指南
python·机器人
Zevalin爱灰灰3 小时前
现代密码学 第二章——流密码【上】
密码学
wbs_scy3 小时前
Linux线程同步与互斥(三):线程同步深度解析之POSIX 信号量与环形队列生产者消费者模型,从原理到源码彻底吃透
java·开发语言