☰
CTF RSA解题工具实战:从参数提取到明文还原的完整流程
2026/10/16 3:44:49 网站建设 项目流程

简介:轩禹CTF_RSA工具3.6.1.zip 是一套面向CTF竞赛选手与密码学爱好者的RSA专项解题工具集,聚焦各类RSA攻击场景的脚本化实现,适合具备一定数论与Python基础、希望系统掌握RSA题型解法的中高级选手。压缩包共32个文件,约26.83MB,以18个Python脚本为核心,辅以3个可执行程序、2个p文件、2个log日志、2个ini配置及readme、changes等说明文档,整体结构清晰,便于按攻击类型检索调用。内容覆盖维纳攻击及其变形、Boneh Durfee攻击、低加密指数与低加密广播攻击、dp/dq泄露、p/m高位泄露、共模攻击、模不互素、e与phi不互素、Rabin算法、AMM算法、有限域开方小e、Franklin-Reiter相关攻击等常见与进阶题型,并集成yafu大数分解工具及配套配置与素数表。目前已有2074人学习下载,可作为赛前刷题、脚本复用与攻击思路对照的实用参考。

1. 从一次翻车说起:为什么我劝你手边常备一个 RSA 专用工具

去年打一场线上赛,密码学方向放了三道 RSA。第一道是常规的小公钥指数广播攻击,第二道是共模攻击,第三道上来就给了一个 4096 位的 n、一个 e=65537,还有一堆看起来毫无规律的密文。我当时的第一反应是掏脚本——结果翻了半天,发现自己之前写的那些一次性脚本散落在四五个文件夹里,有的依赖旧版本的库跑不起来,有的参数写死了改起来费劲。等我把环境理顺,比赛已经过去四十分钟。

那场比赛之后我做了一件事:把 RSA 相关的常见攻击场景、参数处理、批量解密流程,全部收敛到一个能直接跑的工具里。后来拿到这份「轩禹CTF_RSA工具3.6.1」,用下来的感受是——它基本覆盖了我当初想要的那个形态。这篇不是工具说明书,而是把我自己打 RSA 题时踩过的坑、判断题目该走哪条路的依据、以及这个工具在实际场景里怎么用,一次讲清楚。如果你也在为「RSA 题目拿到手不知道从哪下手」发愁,下面的内容应该能帮你省下不少试错时间。

2. RSA 题目的四条主线:先判断类型,再谈工具

2.1 从公钥参数反推攻击路径

拿到一道 RSA 题,我一般不会急着写代码,而是先看三个东西:n 的位数、e 的取值、有没有给额外的提示文件。这三个信息基本决定了你该走哪条路。

n 的位数是最直观的信号。如果 n 小于 512 位,直接上因数分解,不用犹豫。如果 n 在 1024 位以上但 e 特别小(比如 e=3),那大概率是低加密指数攻击,考虑开方或者广播攻击。如果 e 很大,接近 n 的位数,那要想一想是不是低解密指数攻击(Wiener 攻击或者 Boneh-Durfee)。

e 的取值也有讲究。e=65537 是标准值,通常意味着出题人没在 e 上做文章,重点在 n 或者密文的构造上。e=3 是低加密指数攻击的经典信号。e=1 就更直接了,密文直接等于明文。还有一种情况是给了多组 (n, e, c),这时候要想到共模攻击或者广播攻击。

额外提示文件往往是最容易被忽略的。有的题目会给一个 output.txt,里面除了常规参数还藏了 p 的高位或者低位;有的会给一个 encrypted 文件和一个 hint 文件。我见过最坑的一种是提示信息藏在文件名的十六进制编码里,你不注意就漏了。

提示:拿到题目先别动手,花两分钟把 n、e、c 的位数和格式看清楚,比上来就写脚本效率高得多。

2.2 常见攻击类型的识别特征

把常见的 RSA 攻击类型和它们的识别特征整理成一张表,方便对照:

攻击类型识别特征核心思路
小 n 分解n 小于 512 位直接因数分解
低加密指数e=3 且 m 较小对 c 开 e 次方
广播攻击多组 (n, c) 且 e 相同中国剩余定理
共模攻击多组 (n, c) 且 n 相同、e 不同扩展欧几里得
低解密指数e 很大接近 nWiener 连分数逼近
因数接近p 和 q 差值很小Fermat 分解
已知高位给了 p 或 q 的部分位Coppersmith 方法

这张表不是让你背,而是让你在拿到题目时能快速定位。我自己的习惯是:先看 n 的位数排除小 n 分解,再看 e 的值判断是不是低加密指数或者低解密指数,最后看给了几组数据判断是不是广播或者共模。三步走完,方向基本就定了。

2.3 工具在流程中的位置

搞清楚攻击类型之后,工具的价值就体现出来了。以这份 RSA 工具为例,它把常见的攻击场景做成了独立的功能模块,你不需要自己去实现扩展欧几里得或者中国剩余定理,输入参数直接出结果。

但要注意一点:工具是加速器,不是替代品。你得先知道这道题该用哪种攻击,再去工具里找对应的功能。如果连方向都判断错了,工具再快也没用。我见过有人拿到共模攻击的题,非要去试因数分解,跑了半小时没结果,最后发现 n 是同一个,白白浪费时间。

工具里我比较常用的几个功能:大数分解(支持试除法和 Pollard's rho)、低加密指数开方、共模攻击求解、广播攻击的中国剩余定理计算。这几个覆盖了入门到中等难度的绝大多数场景。

2.4 从题目到脚本的完整链路

一道 RSA 题从拿到手到出 flag,完整的链路是这样的:

第一步,提取参数。把 n、e、c 从题目文件里读出来,注意格式转换。有的题目给的是十六进制,有的是十进制,有的是 base64 编码后的字节串。这一步看起来简单,但格式转换出错是最常见的翻车点。

第二步,判断攻击类型。按前面说的三步走,确定方向。

第三步,调用工具或写脚本求解。简单的场景直接用工具的功能模块,复杂的场景可能需要组合多个步骤。

第四步,验证结果。解出来的 m 要转成字节串,看看是不是可读的 flag 格式。如果不是,检查一下是不是漏了填充或者编码方式不对。

这个链路里,第一步和第四步是最容易出问题的。参数提取错了,后面全错;结果验证没做,可能拿着一个错误的明文还以为自己算对了。

3. 工具实战:从参数提取到明文还原

3.1 环境准备与基础参数处理

这份工具是 Python 写的,依赖几个常见的数学库。我一般会在一个独立的虚拟环境里跑,避免和系统里的其他版本冲突。

# 创建虚拟环境 python3 -m venv rsa_env source rsa_env/bin/activate # 安装依赖(根据工具目录下的 requirements.txt) pip install -r requirements.txt

装完之后,先跑一个最简单的测试用例,确认环境没问题。我一般会自己构造一组小素数,手动算一遍 n、e、c,然后用工具解一下,看看结果对不对。

# 构造测试用例 p = 61 q = 53 n = p * q # 3233 e = 17 m = 65 # 明文 c = pow(m, e, n) # 密文 print(f"n={n}, e={e}, c={c}") # 输出:n=3233, e=17, c=2790

这组参数丢进工具里,应该能直接分解出 p 和 q,然后算出 d,最后还原出 m=65。如果这一步跑不通,后面的复杂场景就不用试了。

参数处理里有一个细节容易被忽略:n 和 c 的进制。有的题目给的是十六进制字符串,你需要先 int(x, 16) 转成整数;有的给的是字节串,需要先转成整数再处理。工具里一般会有对应的输入选项,但你自己得清楚当前处理的是什么格式。

3.2 低加密指数与共模攻击的复现

低加密指数攻击的场景很典型:e=3,明文 m 比较短,导致 m^3 小于 n,这时候直接对 c 开三次方就能拿到 m。

import gmpy2 # 低加密指数攻击示例 e = 3 c = 12345678901234567890 # 密文 m, exact = gmpy2.iroot(c, e) if exact: print(f"明文整数:{m}") # 转成字节串 m_bytes = int(m).to_bytes((int(m).bit_length() + 7) // 8, 'big') print(f"明文字节:{m_bytes}") else: print("不能直接开方,可能需要其他处理")

这段代码的关键在 gmpy2.iroot 这个函数,它返回两个值:根的整数部分和一个布尔值表示是否精确。如果 exact 为 False,说明 m^e 大于 n,直接开方拿不到正确结果,需要考虑其他攻击方式。

共模攻击的场景是:同一个 n,两组不同的 (e1, c1) 和 (e2, c2),且 e1 和 e2 互质。这时候可以用扩展欧几里得算法求出 s1 和 s2,使得 s1e1 + s2e2 = 1,然后计算 c1^s1 * c2^s2 mod n 就能得到明文。

from Crypto.Util.number import long_to_bytes def common_modulus_attack(n, e1, c1, e2, c2): # 扩展欧几里得求 s1, s2 g, s1, s2 = gmpy2.gcdext(e1, e2) if g != 1: return None # e1 和 e2 不互质,攻击不适用 # 处理 s1 或 s2 为负数的情况 if s1 < 0: s1 = -s1 c1 = gmpy2.invert(c1, n) if s2 < 0: s2 = -s2 c2 = gmpy2.invert(c2, n) m = (pow(c1, s1, n) * pow(c2, s2, n)) % n return long_to_bytes(m) # 使用示例 n = 123456789 e1, c1 = 17, 111 e2, c2 = 65537, 222 result = common_modulus_attack(n, e1, c1, e2, c2) print(result)

这段代码里有两个容易翻车的点。第一,gcdext 返回的 s1 和 s2 可能有一个是负数,负数次幂在模运算里要转成逆元再算。第二,如果 e1 和 e2 不互质,扩展欧几里得的最大公约数不是 1,这时候共模攻击不成立,需要换思路。

工具里这两个攻击都有现成的模块,你只需要把参数填进去就行。但我建议你还是把上面的代码自己跑一遍,理解每一步在做什么。工具是黑匣子,你自己写过一遍,出问题的时候才知道去哪排查。

3.3 批量处理与结果验证

比赛里有时候会给你一批密文,每个密文对应一个不同的 n 或者不同的 e。这时候手动一个个处理效率太低,需要批量脚本。

import json # 假设题目给了一个 JSON 文件,包含多组参数 with open('challenges.json', 'r') as f: data = json.load(f) results = [] for item in data['challenges']: n = int(item['n'], 16) e = int(item['e']) c = int(item['c'], 16) # 根据 e 的值判断攻击类型 if e == 3: m, exact = gmpy2.iroot(c, 3) if exact: results.append(long_to_bytes(m)) continue # 其他情况调用工具的函数 # ... print(results)

批量处理的核心是自动化判断和异常处理。你不能假设每组数据都能用同一种方法解出来,得根据 e 的值、n 的位数动态选择攻击方式。工具里一般会有对应的批量接口,但你自己得清楚每组数据走的是哪条路。

结果验证这一步,我一般会做两件事:一是检查解出来的字节串是不是可打印字符,二是看有没有 flag 的固定格式(比如 flag{ 开头,} 结尾)。如果解出来是一堆乱码,大概率是中间某一步算错了。

注意:解出来的明文可能是经过填充的,比如 PKCS#1 v1.5 填充,你需要把填充去掉才能看到真正的 flag。工具里一般会有去填充的选项,但你要知道填充的格式是什么样的。

4. 避坑指南:RSA 解题中最容易翻车的五个地方

4.1 参数进制搞混导致全盘皆错

现象:脚本跑完了,结果是一堆乱码,或者直接报错说无法转换。

原因:n、e、c 的进制没统一。有的题目给的是十六进制字符串,你当成十进制处理了;有的给的是字节串,你没转成整数就直接运算。

解决:拿到参数先打印出来看一眼。十六进制的字符串通常会有 0x 前缀或者只包含 0-9a-f 的字符;十进制的就是纯数字;字节串会有 b'...' 的格式。确认格式之后再统一转成整数处理。我自己的习惯是在脚本开头加一行 print(type(n), n) 确认一下。

4.2 e 和 phi 不互质导致求逆失败

现象:调用 invert(e, phi) 的时候报错,说没有逆元。

原因:e 和 phi(n) 不互质,最大公约数不是 1。这种情况在标准 RSA 里不应该出现,但出题人可能故意构造了这种参数。

解决:先算一下 gcd(e, phi),如果不是 1,说明 e 和 phi 有公因子。这时候需要把 e 和 phi 同时除以公因子,分别求解,再用中国剩余定理合并结果。工具里一般会有对应的处理逻辑,但你要知道为什么会出现这个问题。

4.3 低加密指数攻击时 m^e 大于 n

现象:对 c 开 e 次方,结果不是精确的整数,或者解出来的明文不对。

原因:m^e 大于 n,做了模运算之后 c 不再等于 m^e,直接开方拿不到正确的 m。

解决:这种情况需要考虑其他攻击方式。如果有多组密文对应同一个 m 但不同的 n,可以用广播攻击;如果 e 比较小但 m 比较大,可以尝试 Coppersmith 方法。工具里会有对应的选项,但你要先判断清楚当前场景适不适用。

4.4 共模攻击时 e1 和 e2 不互质

现象:扩展欧几里得算出来的最大公约数不是 1,无法继续。

原因:e1 和 e2 有公因子,不满足共模攻击的前提条件。

解决:如果 e1 和 e2 的最大公约数是 g,可以先对 c1 和 c2 分别开 g 次方,然后再用共模攻击求解。或者考虑其他攻击方式。工具里一般会提示这种情况,但你要知道背后的原因。

4.5 明文还原时编码方式不对

现象:解出来的整数转成字节串之后是乱码,或者长度不对。

原因:明文的编码方式不是标准的 UTF-8 或者 ASCII,可能是 base64 编码后的结果,或者经过了其他转换。

解决:先尝试直接转字节串,看看是不是可打印字符。如果不是,尝试 base64 解码或者十六进制解码。工具里一般会有多种编码选项,但你要根据题目的提示判断用哪种。我自己的习惯是先把整数转成十六进制字符串,然后手动看一下字节分布,再决定用什么编码方式。

5. 从工具到习惯:我的 RSA 解题流程收尾

这份工具用下来,最大的价值不是它实现了多少种攻击,而是它帮我固化了一套解题流程。我现在拿到任何一道 RSA 题,都会按这个顺序走一遍:

先花两分钟看参数,n 的位数、e 的取值、给了几组数据、有没有额外提示文件。然后对照攻击类型表,确定这道题该走哪条路。接着打开工具,找到对应的功能模块,把参数填进去。最后验证结果,确认解出来的明文格式正确。

这套流程看起来简单,但每一步都有讲究。看参数的时候要细心,进制、格式、位数都不能漏。判断类型的时候要果断,不要在一棵树上吊死。用工具的时候要清楚它在做什么,不要把它当黑匣子。验证结果的时候要严格,乱码就是乱码,不要骗自己说可能是编码问题。

工具本身也在迭代,3.6.1 这个版本比我之前用的旧版多了几个功能,比如对 Coppersmith 方法的支持更完善了,批量处理的接口也更顺手。但工具终究是辅助,真正决定你能不能解出题目的,是你对 RSA 原理的理解和对攻击场景的熟悉程度。

回过头看,我当初翻车的那场比赛,问题不在于没有工具,而在于我没有一套固定的流程。工具可以帮你省时间,但前提是你知道什么时候该用它、怎么用它。希望这份工具和上面的经验,能帮你在下一场 CTF 里少走一点弯路。

本文还有配套的精品资源,点击获取

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

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

立即咨询