从CTF实战解析RSA加密:大数分解原理与Python工具链应用
2026/8/28 4:22:35 网站建设 项目流程

1. 项目概述:从一道CTF题看RSA加密的实战拆解

今天想和大家复盘一道来自BUUCTF平台的每日打卡题,日期是2021年5月18日。这道题本身是一个典型的RSA加密挑战,但它的价值远不止于解出一个Flag。对于刚接触CTF(Capture The Flag)安全竞赛的朋友,或者对密码学、尤其是非对称加密RSA感兴趣的学习者来说,这道题提供了一个近乎完美的微型实战场景。它不像那些庞杂的综合渗透测试,而是聚焦于一个核心点:给你加密后的数据(密文)和公开的密钥参数,如何利用数学原理和工具将其还原为原始信息(明文)。这个过程,就是一次对RSA算法从理论到实践的深度穿越。

RSA算法作为现代网络通信的基石之一,从HTTPS的握手到软件的数字签名,无处不在。但在教科书里,它是一堆数学公式;在CTF题里,它变成了一个等待被“打开”的锁。这道2021年的老题,恰好卡在了一个非常经典的知识点上,涉及到大数分解的脆弱性。通过拆解它,我们不仅能学会使用opensslPythonCrypto库等工具,更能直观理解“为什么参数的选择如此重要”、“为什么过短的密钥不再安全”。无论你是想入门CTF密码学方向,还是希望加固自己对加密算法的理解,跟着这道题的思路走一遍,收获会比单纯看理论大得多。下面,我就以解题为主线,把其中涉及的核心原理、工具操作和避坑经验毫无保留地分享出来。

2. 题目核心与RSA算法原理快速回顾

拿到任何一道CTF的密码学题目,尤其是RSA,第一步永远不是急着运行脚本,而是仔细阅读题目描述,提取所有给出的参数。通常,题目会提供一个flag.enc(加密后的flag文件)和一个public.key(公钥文件),有时也会直接给出模数n和公钥指数e。这道题便是如此。

2.1 RSA加密的基本流程

RSA的安全性建立在“大数分解难题”之上。简单来说,我给你两个大质数pq的乘积n,你想从n倒推出pq,在计算上是极其困难的。整个加解密过程围绕几个核心参数展开:

  1. 选择两个大质数pq
  2. 计算模数n = p * qn的长度(比特数)就是常说的密钥长度,如2048位。
  3. 计算欧拉函数φ(n) = (p-1)*(q-1)。这个值在生成私钥时至关重要,但必须保密。
  4. 选择公钥指数e,通常是一个较小的质数,如65537 (0x10001),满足1 < e < φ(n)eφ(n)互质。
  5. 计算私钥指数d,是eφ(n)的模逆元,即满足(d * e) % φ(n) = 1d就是私钥的核心部分。
  6. 公钥:由(n, e)组成,用于加密。加密过程:ciphertext = plaintext ^ e mod n
  7. 私钥:由(n, d)组成,用于解密。解密过程:plaintext = ciphertext ^ d mod n

在CTF题目中,我们作为攻击者,目标就是利用题目可能给出的任何弱点(比如n太小、pq很接近、e很大导致d很小等),来恢复出私钥d,从而解密flag.enc

2.2 本题的突破口分析

对于“BUUCTF 每日打卡 2021-5-18”这道题,其经典之处在于,它给出的RSA公钥中的模数n并不大。通过openssl命令解析公钥文件后,我们可以直接得到ne。如果n的数值较小(例如小于768位或1024位),那么在当今的计算能力下,完全有可能在可接受的时间内(几秒到几分钟)通过在线大数分解数据库(如 factordb.com)或本地工具(如 yafu)将其分解为pq

一旦成功分解n得到pq,我们就能计算出φ(n) = (p-1)*(q-1),进而根据公式d = gmpy2.invert(e, φ(n))计算出私钥指数d。有了私钥,解密便是水到渠成。所以,这道题的核心解题链路非常清晰:获取参数 -> 分解n -> 计算私钥 -> 解密。它完美地演示了当RSA密钥长度不足时,其安全性是如何土崩瓦解的。

3. 实操步骤详解:从公钥到Flag

下面,我们进入具体的操作环节。我会假设你有一个基本的Linux环境(Windows下可用WSL或Git Bash),并安装了Python3和必要的库。

3.1 第一步:提取公钥中的n和e

通常题目会给出一个public.pempub.key文件。我们使用OpenSSL这个瑞士军刀来查看其内容。

# 查看公钥的详细文本信息,可以看到模数和指数(Base64编码格式) openssl rsa -pubin -in public.pem -text -noout

执行后,你会看到类似这样的输出:

Public-Key: (256 bit) Modulus: 00:c2:63:7f:45:be:... (很长一串十六进制) Exponent: 65537 (0x10001)

这里的关键信息是:

  • Modulus: 这就是模数n,以十六进制表示。注意,输出的十六进制可能带有冒号分隔,也可能没有。我们需要将其转换为一个十进制大整数。
  • Exponent: 公钥指数e,绝大多数情况下是65537。

实操要点:OpenSSL输出的十六进制,我们需要将其整理成一个连续的字符串(去掉冒号和空格,并去掉可能存在的00:前缀),然后通过Python转换为十进制整数。例如,如果输出是00:c2:63:7f,那么有效的十六进制串是c2637f

3.2 第二步:分解模数n

这是解题最关键的步骤。将上一步得到的十进制大整数n,尝试进行因数分解。

  1. 在线分解(推荐首选):访问factordb.com这个网站,直接将n的十进制数值粘贴到查询框。如果这个n曾经被其他人分解过,或者它本身很小,数据库里很可能已经有结果了。网站会直接返回pq
  2. 本地工具分解:如果在线数据库没有结果,或者你想在离线环境下操作,可以使用yafu这个强大的因数分解工具。对于小于256位的nyafufactor()函数通常能很快搞定。
    # 启动yafu交互界面 ./yafu # 在yafu提示符下输入 factor(你的n的十进制数值)
    分解成功后,它会输出pq的值。

注意事项:如果题目中的n非常大(比如2048位以上),那么这道题大概率不是考分解,而是考察其他RSA攻击方式,如共模攻击、低加密指数攻击、维纳攻击等。本题因为是“每日打卡”难度,且年份较早,所以n较小,分解是可行路径。

3.3 第三步:计算私钥并解密

成功获取pq后,我们就可以在Python中计算私钥并解密了。这里需要用到gmpy2pycryptodome库来处理大数运算。

首先,确保安装必要库:

pip install pycryptodome gmpy2

然后,使用以下Python脚本进行解密:

from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP from Crypto.Util.number import long_to_bytes, bytes_to_long import gmpy2 # 1. 填入你从题目中获取的值 n = 123456789... # 替换为你的模数n (十进制大整数) e = 65537 # 通常是这个值 p = ... # 替换为分解得到的质数p q = ... # 替换为分解得到的质数q # 2. 计算私钥参数 phi = (p - 1) * (q - 1) d = int(gmpy2.invert(e, phi)) # 计算私钥指数d # 3. 构建私钥对象 key = RSA.construct((n, e, d, p, q)) # 4. 读取加密的flag文件 with open('flag.enc', 'rb') as f: ciphertext = f.read() # 5. 解密(注意填充方式,CTF中常见PKCS1_v1_5或无填充) # 方案A:如果加密使用了PKCS1_OAEP填充(现代标准) cipher = PKCS1_OAEP.new(key) plaintext = cipher.decrypt(ciphertext) print(f"Flag (PKCS1_OAEP): {plaintext.decode()}") # 方案B:如果加密是简单的“明文^e mod n”(即无填充或自定义填充),需要直接计算 # cipher_int = bytes_to_long(ciphertext) # plain_int = pow(cipher_int, d, n) # plaintext = long_to_bytes(plain_int) # print(f"Flag (Raw): {plaintext.decode()}")

核心细节解析

  • 填充方案:这是解密时最容易出错的地方。标准的RSA加密为了安全性,会对明文进行填充(如PKCS1_v1_5或OAEP)。CTF题目中,为了简化,有时会使用无填充的“教科书式RSA”。如果使用PKCS1_OAEP解密报错ValueError: Ciphertext with incorrect length.,很可能意味着加密时未使用标准填充。此时需要尝试方案B的直接模幂运算。
  • construct方法RSA.construct()函数非常强大,它允许我们直接传入(n, e, d, p, q)等元组来构建一个RSA密钥对象,而无需从文件加载。
  • 文件读取:务必以二进制模式('rb')读取flag.enc,因为密文是二进制数据。

运行脚本后,正确的Flag通常就会打印在终端上,格式可能为flag{...}BUUCTF{...}

4. 深入原理:为什么分解n就能破解RSA?

上面我们完成了实操,但知其然更要知其所以然。为什么分解n是RSA的“命门”?这需要回到RSA的数学基础。

RSA的解密密钥d,是通过公式d ≡ e^(-1) (mod φ(n))计算得到的。而φ(n) = (p-1)*(q-1)。在整个公钥体系中,n是公开的,但pq是保密的。因此,攻击者无法直接计算φ(n)

大数分解难题的假设是:给定一个由两个大质数相乘得到的合数n,想要在合理时间内找出原来的pq是计算不可行的。只要这个假设成立,攻击者就无法从公开的n求得φ(n),也就无法计算出私钥d

然而,这个“计算不可行”是相对于n的长度而言的。随着计算机计算能力的提升和算法(如数域筛法)的改进,过去认为安全的密钥长度,现在可能已经不再安全。例如,早在1999年,512位的RSA密钥就被成功分解。目前,对于一般用途,2048位是基准线,需要长期安全的应用则推荐3072位或4096位。

这道题使用的n长度较短,正是刻意违背了“大数”的前提,使得分解在瞬间完成,从而直观展示了密钥长度不足的风险。在实际的CTF比赛中,你会遇到各种围绕nedpq关系做文章的变种题,例如:

  • 共模攻击:相同的n,不同的e,加密了同一明文。
  • 低加密指数攻击e非常小(如3),且明文也很小,导致m^e < n,加密等于没加密。
  • 维纳攻击:当私钥d相对较小时,可以通过连分数逼近的方法在多项式时间内破解。
  • p和q过于接近:导致|p-q|很小,可以通过费马分解法快速分解n

理解这些攻击的本质,都离不开对RSA数学模型的深刻把握。这道基础分解题,是打开这扇大门的第一把钥匙。

5. 工具链与常见问题排查

在实战中,工具用得不顺手或者遇到意外错误是常事。这里我整理了一份从解题到调试的常用工具链和问题排查指南。

5.1 必备工具链清单

  1. OpenSSL: 处理各种编码、查看解析密钥、转换格式的万金油。除了查看公钥,还能将公钥/私钥在不同格式(PEM, DER)间转换。
  2. Python3 + Crypto/Pycryptodome库: 主要的计算和脚本编写环境。PycryptodomePyCrypto的一个维护良好的分支,功能更全。
  3. GMPY2: 一个提供高精度快速大数运算的Python库,在计算模逆、大数幂模运算时比Python原生整数运算快得多,处理非常大的数字时几乎是必备的。
  4. Factordb (在线) / Yafu (本地): 如前所述,用于分解n。对于本地分解,Yafu非常强大,它集成了多种先进的分解算法。
  5. RSACTFTool / RsaCtfTool: 这是一个用Python写的集成化RSA攻击工具包。当你面对一道RSA题没有头绪时,可以尝试用它自动攻击。它内置了数十种攻击方式,包括低指数、共模、维纳攻击、以及尝试从各种格式的文件中自动提取参数等。对于新手来说,这是一个“开箱即用”的神器。
    python RsaCtfTool.py --publickey public.pem --uncipherfile flag.enc

5.2 高频错误与解决方案

即使按照步骤操作,你也可能会遇到以下问题。这里是我的“踩坑”记录:

问题1:openssl rsa -pubin -in public.pem -text -noout命令报错“Expecting: PUBLIC KEY”。

  • 原因:公钥文件的格式不对。可能是文件开头结尾的标记不正确,或者它实际上是一个包含公钥的证书(.crt文件)。
  • 解决
    • 检查文件内容。标准的PEM格式公钥以-----BEGIN PUBLIC KEY-----开头。
    • 如果是证书,使用命令openssl x509 -in public.crt -pubkey -noout | openssl rsa -pubin -text -noout来提取并查看公钥。
    • 有时题目给的公钥是SSH格式(ssh-rsa AAAAB3...),需要先转换为PEM格式。可以用ssh-keygen -f public.key -e -m pem > public.pem转换。

问题2:分解n后,用Python脚本解密,报错ValueError: Ciphertext with incorrect length

  • 原因:这是最典型的填充模式不匹配错误。PKCS1_OAEP解密期望密文长度等于密钥长度(字节数)。如果加密时使用的是无填充或PKCS1_v1_5填充,用OAEP解密就会失败。
  • 解决
    • 尝试使用PKCS1_v1_5模式:from Crypto.Cipher import PKCS1_v1_5; cipher = PKCS1_v1_5.new(key)
    • 如果还不行,大概率是“教科书式RSA”(无填充)。直接使用plain_int = pow(cipher_int, d, n)计算,如3.3节中的方案B。注意,ciphertext需要先通过bytes_to_long()转换成整数。

问题3:计算出的私钥d是负数,或者解密得到乱码。

  • 原因
    1. n分解错误:这是最根本的原因。请务必核对p * q是否等于原始的n
    2. φ(n)计算错误:确保是(p-1)*(q-1),而不是p*q-1或其他。
    3. 密文文件读取错误:确保以二进制模式(‘rb’)读取,且文件内容完整。
    4. e值错误:虽然99%是65537,但仍有题目会使用其他e(如3、17)。用OpenSSL确认e的值。
  • 解决:建议写一个简单的验证脚本:
    print(f"Check n == p*q: {n == p*q}") print(f"Check (d*e) % phi == 1: {(d*e) % phi == 1}")
    如果第一个检查为False,立刻回头检查分解步骤。如果第二个为False,检查phi的计算和gmpy2.invert函数的使用。

问题4:使用在线分解网站没有结果,yafu分解也很慢。

  • 原因:说明这道题的n可能并不小,或者出题人特意选用了能抵抗快速分解的素数。这道题可能不是考分解。
  • 解决:重新审视题目。检查e是否特别大(可能导致d小,适用维纳攻击)?是否有多个公钥文件(可能考共模攻击)?密文c是否非常小(可能考低加密指数攻击)?此时,应该转向使用RsaCtfTool进行自动化测试,或者系统学习其他RSA攻击模型。

6. 从解题到精通:RSA在CTF中的进阶考点

解出这道基础题只是一个开始。BUUCTF以及其他CTF平台上有大量更深入的RSA题目,它们像一个个精心设计的谜题,考察你对算法各个维度的理解。以下是一些常见的进阶考点和思路:

6.1 多种攻击场景与识别特征

攻击类型题目典型特征核心思路与工具
模数分解模数n较小(如<1024位),或pq有缺陷(如相近、光滑数)。使用factordb、yafu分解。
共模攻击给出两个或多个公钥(n, e1),(n, e2),加密了同一明文m利用扩展欧几里得算法找到re1 + se2 = 1,计算c1^r * c2^s mod n = m
低加密指数攻击公钥指数e很小(如3),并且明文m满足m^e < n直接对密文ce次方根。
低加密指数广播攻击相同的明文m,用相同的e但不同的n加密,得到多个密文c_i利用中国剩余定理(CRT)求解满足所有同余式的m^e,再开方。
维纳攻击私钥d较小,满足d < (1/3) * n^(1/4)利用连分数展开逼近e/n,来快速计算出d
p-1光滑或p+1光滑素数p满足p-1p+1的因子都是小质数(光滑数)。使用Pollard‘s p-1 或 Williams‘s p+1 算法分解n
泄露部分私钥题目给出了私钥d的一部分位,或者pq的高位/低位。使用Coppersmith定理进行格基规约攻击,恢复完整的密钥。

6.2 实战思维养成

面对一道新的RSA题,我通常会遵循以下排查流程:

  1. 信息收集:用opensslbinwalkstrings等工具仔细查看所有给定文件,不放过任何注释、额外数字或文本。
  2. 参数提取:准确提取所有n,e,c(密文),以及任何可能的p,q,d的片段。
  3. 初步尝试
    • 尝试分解n(factordb)。
    • 检查e是否很小(如3)。
    • 检查是否有多个ne
  4. 工具辅助:将参数喂给RsaCtfTool,让它自动尝试所有已知攻击。
  5. 数学分析:如果工具无效,回到数学本身。分析n的位数,ec的大小关系,思考可能存在的数学关系(如d小,pq有特殊关系)。
  6. 搜索与学习:将题目中的关键特征(如n的特殊值、e的特定值)或错误信息进行搜索,很可能在CTF Writeup(解题报告)中找到类似思路。

6.3 资源推荐与持续学习

  • 练习平台:BUUCTF、CTFHub、攻防世界(ADWorld)都提供了丰富的密码学题目,按难度分类,非常适合循序渐进。
  • 学习资料:除了经典的《应用密码学》外,我强烈推荐阅读CTF选手写的Writeup。GitHub上有很多集合,搜索“CTF RSA Writeup”能找到大量实战案例。
  • 社区交流:遇到难题时,在相关的CTF社区或论坛(如看雪论坛、先知社区)提问,往往能获得高手的关键指点。

回过头看这道“每日打卡”题,它就像RSA世界的“Hello World”。通过它,我们跑通了一个完整的“识别-分析-攻击-解密”流程,掌握了最基本的工具链。更重要的是,我们理解了RSA安全性的核心假设及其脆弱条件。在后续挑战更复杂的题目时,你会不断重温并深化这些基础概念。密码学学习没有捷径,就是一道题一道题地啃,一个概念一个概念地磨。每解一道题,你对那些看似抽象的数学原理的理解就会更具体一分。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询