rsa Writeup by AI

题目信息

  • 题目来源: 攻防世界 (Adworld)
  • 题目类型: Crypto (密码学)
  • 题目名称: rsa

题目描述

本题是一个 RSA 加密挑战,提供了 RSA 公钥文件 (pubkey.pem) 和加密后的 flag 文件 (flag.enc),需要解密获取原始 flag。

考点分析

  1. RSA 公钥解析: 从 PEM 格式文件中提取 n 和 e
  2. Fermat 分解攻击: 当 RSA 的两个素数 p 和 q 非常接近时,可以使用 Fermat 分解法快速分解大整数 n
  3. RSA 解密: 使用私钥 d 对密文进行解密
  4. 数据编码转换: 处理二进制文件和字符串转换

解题思路

1. 分析公钥

首先读取 pubkey.pem 文件,提取 RSA 公钥参数:

  • n: 4096 位的大整数
  • e: 65537 (常用的 RSA 指数)

2. 分解 n

由于 n 是 4096 位,无法直接分解。但题目设计时可能让 p 和 q 很接近,因此尝试使用 Fermat 分解法

Fermat 分解原理:
如果 n = p × q,且 p ≈ q,那么可以令:

  • a = ⌈√n⌉
  • b² = a² - n

通过不断递增 a,直到 b² 成为完全平方数,此时:

  • p = a - b
  • q = a + b

3. 计算私钥

一旦分解得到 p 和 q,就可以计算:

  • φ(n) = (p-1)(q-1)
  • d = e⁻¹ mod φ(n)

4. 解密 flag

使用 RSA 解密公式:m = c^d mod n

然后将解密结果转换为字符串格式。

详细步骤

步骤 1: 导入必要的库

from Crypto.PublicKey import RSA
import gmpy2
from libnum import n2s, s2n
import math

步骤 2: 读取公钥

with open('pubkey.pem', 'r') as f:
    pubkey = RSA.import_key(f.read())

n = pubkey.n
e = pubkey.e

步骤 3: Fermat 分解

def fermat_factor(n):
    a = math.isqrt(n) + 1
    for i in range(10**6):
        b2 = (a+i)*(a+i) - n
        if b2 < 0:
            continue
        b = gmpy2.iroot(b2, 2)
        if b[1]:  # b2 是完全平方数
            p = (a+i) - b[0]
            q = (a+i) + b[0]
            return p, q
    return None, None

p, q = fermat_factor(n)

步骤 4: 计算私钥并解密

phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)

with open('flag.enc', 'rb') as f:
    cipher_bytes = f.read()

c = int.from_bytes(cipher_bytes, 'big')
m = pow(c, d, n)

flag_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
flag = flag_bytes.decode('utf-8', errors='ignore')

步骤 5: 提取 flag

从解码结果中查找符合 flag{...} 格式的内容。

完整代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
攻防世界 Crypto 12-rsa 解题脚本
考点:RSA 公钥解析与 Fermat 分解攻击
当 RSA 的两个素数 p 和 q 非常接近时,可以使用 Fermat 分解法快速分解 n
"""

from Crypto.PublicKey import RSA
import gmpy2
from libnum import n2s, s2n
import math

def fermat_factor(n):
    """Fermat 分解法:适用于 p 和 q 很接近的情况"""
    a = math.isqrt(n) + 1
    b2 = a*a - n
    
    # 尝试最多 10^6 次
    for i in range(10**6):
        b2 = (a+i)*(a+i) - n
        if b2 < 0:
            continue
        b = gmpy2.iroot(b2, 2)
        if b[1]:
            p = (a+i) - b[0]
            q = (a+i) + b[0]
            return p, q
    
    return None, None

def solve():
    # 读取公钥
    with open('pubkey.pem', 'r') as f:
        pubkey = RSA.import_key(f.read())
    
    n = pubkey.n
    e = pubkey.e
    
    print("=" * 60)
    print("攻防世界 Crypto 12-rsa 解题")
    print("=" * 60)
    print(f"\n公钥参数:")
    print(f"n 的位数:{n.bit_length()} bits")
    print(f"e = {e}")
    
    # 使用 Fermat 分解
    print("\n使用 Fermat 分解法分解 n...")
    p, q = fermat_factor(n)
    
    if p and q:
        print(f"✓ 分解成功!")
        print(f"p = {p}")
        print(f"q = {q}")
        
        # 验证
        assert p * q == n, "分解错误!"
        print("✓ 验证通过:p * q = n")
        
        # 计算私钥 d
        phi = (p - 1) * (q - 1)
        d = gmpy2.invert(e, phi)
        print(f"\n私钥 d 已计算")
        
        # 读取密文
        with open('flag.enc', 'rb') as f:
            cipher_bytes = f.read()
        
        c = int.from_bytes(cipher_bytes, 'big')
        
        # RSA 解密:m = c^d mod n
        print("\n解密中...")
        m = pow(c, d, n)
        
        # 尝试多种方式转换
        print("\n尝试解码明文...")
        
        # 方法 1:直接转换为字节
        try:
            flag_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
            
            # 尝试解码为字符串
            try:
                flag = flag_bytes.decode('utf-8', errors='ignore')
                
                # 提取有效的 flag 格式
                if 'flag{' in flag:
                    start = flag.find('flag{')
                    end = flag.find('}', start) + 1
                    if end > start:
                        clean_flag = flag[start:end].strip()
                        print(f"\n✅ 最终 Flag: {clean_flag}")
                        return clean_flag
            except:
                pass
        except Exception as ex:
            print(f"方法 1 失败:{ex}")
        
        return None
    else:
        print("✗ Fermat 分解失败")
        return None

if __name__ == "__main__":
    solve()

总结

关键知识点

  1. Fermat 分解攻击适用条件: 当 RSA 的两个素数 p 和 q 非常接近时(即 |p-q| 很小),可以使用 Fermat 分解法在合理时间内分解 n
  2. 为什么 p 和 q 接近会有危险: 因为 Fermat 分解的时间复杂度取决于 |p-q| 的大小,当两者接近时,只需要很少的迭代次数就能找到因子
  3. 实际 RSA 应用中的防范: 在实际应用中,生成 RSA 密钥时应确保 p 和 q 足够随机且相差较大,避免此类攻击

解题技巧

  • 遇到大整数分解问题时,先检查是否有特殊性质(如完全平方数、接近平方根等)
  • 对于 CTF题目,如果是 RSA 相关且 n 不是特别大(如 4096 位以下),通常存在某种数学上的弱点
  • Fermat 分解是一种简单有效的分解方法,应该作为首选尝试

用户提问记录

问题 1: 如何从 PEM 文件中提取 RSA 公钥参数?

解答: 使用 Python 的 Crypto.PublicKey.RSA 模块,通过 RSA.import_key() 函数读取 PEM 文件,然后访问 .n.e 属性即可获取模数和指数。

问题 2: Fermat 分解的原理是什么?

解答: Fermat 分解基于一个数学事实:任何奇合数都可以表示为两个数的平方差。对于 n = p×q,如果能找到 a 和 b 使得 n = a² - b²,那么 p = a-b,q = a+b。当 p 和 q 接近时,a 会很接近 √n,因此从 √n 开始搜索很快就能找到。

问题 3: 为什么解密后的数据需要特殊处理才能看到 flag?

解答: RSA 解密后得到的是一个大整数,需要转换为字节串。由于 PKCS#1 填充或其他原因,解密结果可能包含非 ASCII 字符,因此需要使用 errors='ignore' 参数来过滤掉无效字符,然后通过查找 flag{ 模式来提取有效内容。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐