
简介面向CTF竞赛选手与密码学初学者的RSA综合工具包聚焦常见RSA攻击题型与自动化解题脚本可在比赛或练习中快速定位解题思路并验证结果。压缩包共32个文件以18个Python脚本为主体覆盖维纳攻击、低加密指数攻击、共模攻击、dp/dq泄露、Rabin算法、Boneh Durfee、有限域开方、模不互素、低加密广播攻击等经典场景另含3个exe命令行程序及yafu因子分解组件用于大整数分解、日志记录与配置管理整体大小26.83MB。压缩包内置素数表与ctfprimes等数据文件便于构造测试样例和随机参数。已有2077人学习/下载适用于CTF入门选手快速上手也适合有经验者赛前查漏补缺。工具集还提供AMM算法与特殊题型处理方法覆盖e与phi不互素、p高位泄露、m高位泄露等进阶场景脚本按攻击类型分类、结构清晰能够帮助读者系统梳理RSA题型的攻击脉络提升解题效率。1. 轩禹CTF_RSA工具比赛里最缺的不是RSA原理是解题速度一场CTF比赛留给密码题的时间往往不到半小时RSA又是出现频率最高、套路最多的一类。低加密指数、共模、维纳、费马分解每种攻击单独写脚本都不难难的是在有限时间里判断该用哪一种并且不把参数格式搞错。轩禹CTF_RSA工具3.6.1这类集成工具解决的正是这个问题它把常见RSA攻击收拢成统一的参数入口你只需要提供n、e、c工具按规则尝试还原明文。适合刚入门CTF、想省下反复写脚本时间的新手也适合需要批量验证RSA题思路的进阶选手。下面从解压运行讲起拆开它的攻击分类、参数边界再列出我实际踩过的几个坑。2. 摸清RSA攻击套路工具内置的分类与适用边界工具不是黑匣子。它能在几秒内给出一串明文是因为背后按固定顺序尝试了若干种经典攻击这个顺序通常是「先分解模数再看指数异常再看多组参数关系最后看私钥位数泄露」。理解这个顺序才知道为什么某些参数要单独勾选也才知道自动检测失败后该手动切到哪个模式。RSA题目里真正变化的只有五个量模数n、公钥指数e、密文c有时还有私钥指数d或它的部分片段dp、dq。工具拿到参数后会把每个攻击都当成一个独立模块模块之间用输入条件做闸门。例如低加密指数攻击要求e小于某个阈值共模攻击要求两组数据共享同一个nWiener攻击要求e的位数接近n的位数。闸门不满足模块直接跳过。这也是为什么同一个工具在A题上秒出结果在B题上却什么都给不出来——不是工具坏了是题目根本不满足当前模块的前提。2.1 从n、e、c三个入口判断攻击方向拿到RSA题先别急着打开工具先看一眼三个核心数字的特征。判断方向可以依赖几条经验n的位数低于512位或者n能被小质数整除优先走模数分解。e等于3、5、7这类小值且c的位数明显低于n的位数优先走低加密指数。同一份题目里给了两组(e,c)n完全相同优先共模攻击。e非常大位数接近n同时n在1024到2048位之间优先尝试Wiener攻击它针对的是d偏小的场景。n是p²的结构或者p、q是相邻质数费马分解会在几秒内生效。工具界面上通常把这些判断自动化了但手动过一遍仍然有用。因为自动检测是按固定顺序跑的如果第一优先级的攻击不满足时它直接跳过还好怕的是它误判成另一种攻击然后跑很久。第5章会讲这类误判的具体案例。2.2 工具内置的攻击类型与参数格式绝大多数同类RSA工具都包含下面几张表里的模块输入格式基本统一n、e、c用十进制或十六进制整数dp、dq这类泄露参数单独提供输入槽。攻击类型需要的输入典型成功条件常见失败原因模数分解n, e, cn存在小因子或p、q十分接近n是正常生成的大素数乘积低加密指数e, cm^e n允许直接开e次方e太大或c经过模运算后失真共模攻击n, e1, e2, c1, c2gcd(e1, e2) 1且是同一明文两组密文来自不同明文Wienern, e, cd n^0.25d偏大或n太小导致连分数过短dp/dq泄露n, e, c, dp, dqdp、dq确实由同一私钥生成dp、dq是伪造值要注意一个细节工具的输入槽大多数接收的是整数int不是bytes。同一个十六进制字符串直接填进去和转成int再填进去结果完全不同。我见过不少人在这一步把进制搞错后面所有输出都是乱的。统一的处理办法是不管题目给的是hex、base64还是PEM文件先在脚本里转成int再用这个int填工具。2.3 拿到题目先做的三道工序分类、提取、备份第一步是分类判断题目给的是单组公钥还是多文件结构有没有提示词比如flag格式前缀。第二步是提取写个小脚本把公钥PEM拆成n和e把密文统一成整数。第三步是备份原始数据无论如何先存一份转换过程丢一个前缀0整个题就做不出来了。提取PEM最快的方式是opensslopenssl rsa -pubin -in public.pem -text -noout | head -20这条命令会把PEM公钥里的模数和指数直接打印成可读文本方便肉眼核对。逻辑说明-pubin表示输入是公钥-text要求显示内部结构-noout避免把密钥本体再输出一遍。参数说明如果文件是私钥格式去掉-pubin即可如果报错说不是PEM格式说明文件可能是DER先执行openssl rsa -pubin -inform DER -in public.der -text -noout再看。备份习惯在连续刷题时尤其重要。某次我把题目给的十六进制密文手工转成了十进制结果少复制了一位后面的明文怎么解都是乱码最后回看原始文件才发现问题。原始文件就是后悔药别把转换后的结果覆盖回去。3. 从zip解压到跑通第一道RSA题完整的操作流这一章把第一次使用这个工具的全过程走一遍。假设你刚拿到轩禹CTF_RSA工具3.6.1.zip还没看过帮助文档照着下面的操作流可以跑通一道典型题目。整体流程是解压、检查依赖、准备输入参数、执行攻击、处理输出。3.1 解压、运行环境与快速自检unzip 轩禹CTF_RSA工具3.6.1.zip -d ~/tools/rsa-3.6.1 cd ~/tools/rsa-3.6.1 file *逻辑说明第一行把zip解压到固定目录第二行进入目录第三行用file命令检查每个文件的真实类型。这里有个经验file比直接看扩展名可靠得多它能区分出这是Linux下的ELF可执行文件、Windows下的PE文件还是一个需要解释器运行的Python脚本。参数说明如果你的系统上unzip提示需要装包常见做法是apt install unzip或yum install unzip这个看发行版选一个。如果解压出来的文件名是乱码大概率是zip内编码问题可以尝试unzip -O UTF-8 轩禹CTF_RSA工具3.6.1.zip -d ~/tools/rsa-3.6.1。文件名带中文确实容易踩这个坑我一般先复制一份改成全英文名再解压省得终端编码和系统区域设置打架。解压后先看依赖。工具如果依赖Python库通常会缺gmpy2和pycryptodome。gmpy2提供高精度大整数运算Wiener攻击和开方操作都依赖它pycryptodome负责解析PEM密钥文件。缺哪个装哪个pip install gmpy2 pycryptodome逻辑说明这两行把工具最常调用的两个第三方库装好。参数说明如果你的Python环境是3.9以下或3.12以上的特定版本gmpy2可能没有预编译包这时需要先装编译工具链build-essential再用pip install gmpy2源码编译。这个坑比较隐蔽装不上时先看pip输出里是不是在编译而不是在下载。3.2 从PEM和十六进制文本里抽取n、e、c题目给一个PEM公钥文件和一个hex密文第一步是把它们转成工具能用的整数。我一般用下面这段脚本from Crypto.PublicKey import RSA import re with open(public.pem, rb) as f: pub_data f.read() try: key RSA.import_key(pub_data) n key.n e key.e print(n , n) print(e , e) except ValueError: print(不是标准PEM尝试从文本中提取十六进制) text pub_data.decode(errorsignore) hex_n re.findall(r[0-9a-fA-F]{64,}, text) print(hex_n[:1])逻辑说明脚本优先用RSA.import_key解析PEM解析成功就能直接拿到n和e。解析失败则把文件当文本读用正则抓取长度不少于64个字符的十六进制片段这段通常就是模数n。参数说明errorsignore很关键PEM文件里可能混入不可见字符直接decode会抛异常加上这个参数能保证走到正则分支。密文的转换更简单c int(0x9f2c..., 0) # 密文是hex用0x前缀 n int(00c1a3..., 16) # 模数没带前缀手动指定16逻辑说明int的第二个参数如果是0Python会根据字符串前缀自动判断进制如果手动写16则强制按十六进制解析。参数说明题目里经常出现十六进制字符串前带\x或者没有0x的情况这两种写法都覆盖到了。我建议所有从题目原始材料里转换出来的值都打印一遍和题目预期值核对再填进工具。3.3 按提示输入参数并拿到明文把上一步得到的n、e、c填进工具的输入槽选择自动检测模式执行后会看到输出。命令行版本通常是这个形式python3 xuan_yu_rsa.py --n 0x00c1a3... --e 65537 --c 0x9f2c... --mode auto逻辑说明--n、--e、--c分别接收模数、公钥指数和密文--mode指定攻击模式。参数说明--mode auto表示自动检测其他常见值包括factor、low_e、common_n、wiener分别对应模数分解、低加密指数、共模攻击和Wiener攻击。如果auto没有输出明文就按第2章的判断手动指定模式重跑。我在实际使用中auto模式的成功率在六成左右剩余四成是因为题目混合了多种攻击条件需要手动打开第二个攻击模块。3.4 输出格式与字节转字符串的处理工具的输出有时候是一个大整数有时候是一段hex。flag通常藏在转成字节后的字符串里from Crypto.Util.number import long_to_bytes flag_int 1234567890 # 替换成工具输出的明文整数 flag_bytes long_to_bytes(flag_int) try: print(flag_bytes.decode()) except UnicodeDecodeError: print(flag_bytes.hex())逻辑说明long_to_bytes把大整数按字节切分得到原始字节串。先尝试decode()转成可打印字符失败则输出hex再人工查看。参数说明decode()默认用utf-8而flag内容一般只有可打印ASCII所以一旦出现UnicodeDecodeError说明明文中含有超出ASCII范围的字节这时候看hex反而更清楚不强制转utf-8。4. 高频攻击的单独调优四个常用功能的参数与边界自动检测能覆盖大部分简单题但比赛里常出现需要手动选模式的情况。这一章把四个高频攻击的原理、参数边界和调优方式单独拆开讲重点说清楚每个模块在什么条件下有效、什么条件下会误判。4.1 模数分解费马分解与Pollard rho的参数范围模数分解是RSA题的第一优先级因为一旦n被分解私钥立刻还原。费马分解针对的是p和q距离很近的情况数学依据是n可以写成两个平方数的差。工具里通常给你一个「分解精度」参数实际控制的是循环次数。手动判断适用性的脚本如下import gmpy2 a gmpy2.isqrt(n) 1 b2 a * a - n while not gmpy2.is_square(b2): a 1 b2 a * a - n p a - gmpy2.isqrt(b2) q a gmpy2.isqrt(b2) print(p , p) print(q , q)逻辑说明从sqrt(n)向上取整开始不断调整a直到a² - n成为完全平方数此时n的因子立即分解出来。参数说明这个循环的耗时完全取决于p和q的差值。差值在几千以内时是秒出差值到百万级别就要几分钟差值再大就不适合硬跑应该换Pollard rho或者直接用更重的工具。我一般只看一个数gmpy2.isqrt(n) - p的大小这个差值超过一万就果断放弃费马分解。Pollard rho在工具里适合处理n在512位以下、含有较小因子但不容易试除的情况。它本质是随机寻找碰撞参数上有迭代上限通常默认设成10^6跑超过上限自动放弃。如果你的题目n有不止两个因子分解出第一个因子后需要把n除以该因子再继续工具一般会自动处理这部分。4.2 低加密指数攻击从爆破到字节填充e很小是RSA里最明显的送分信号。攻击成立的数学前提是加密时c m^e mod n如果m^e本来就小于n模运算没有起任何作用直接对c开e次方就能得到m。from gmpy2 import iroot e 3 c 832978... # 替换成题目给的密文 m, exact iroot(c, e) if exact: print(明文整数:, m) else: print(e次方不是整数密文经过了模运算)逻辑说明iroot(c, e)返回两个值第一个是c的e次方根向下取整第二个是布尔值表示开方结果是否完全精确。参数说明如果exact为False说明m^e已经超过了n不能直接开方这时要换思路比如检查m是否只经过了某种填充或者题目是否用了同一e加密多组消息。这类攻击在工具里的参数是「小明文爆破上限」。有些题目虽然e与n不满足直接开方条件但明文本身很短比如只有几字节。工具会尝试从0到一个上限值逐一遍历拼上常见前缀再加密比较。这个上限默认是10^6题目如果明示flag格式为固定前缀可以把这个上限调低加快爆破速度。4.3 共模攻击在拿不到私钥的情况下恢复明文共模攻击的成立条件很苛刻但很常见同一个n两条不同的e都加密了同一条明文。数学上的解法是利用扩展欧几里得找到一组整数a、b使得a*e1 b*e2 1然后用c1^a * c2^b mod n还原明文。from gmpy2 import gcdext from Crypto.Util.number import long_to_bytes e1 65537 e2 65539 c1 123456... c2 654321... n 987654... g, a, b gcdext(e1, e2) if g ! 1: print(gcd(e1, e2) ! 1不满足共模前提) else: m (pow(c1, a, n) * pow(c2, b, n)) % n print(long_to_bytes(m))逻辑说明gcdext返回最大公约数和两组系数。如果最大公约数是1说明两个指数互质表达式成立。pow(c1, a, n)里的a可能是负数Python的三参pow能自动处理模逆运算不需要手动转换。参数说明这里最关键的是确认两条密文确实对应同一条明文题目里如果出现两个不同的c但n相同基本可以断定是共模。如果gcd不是1最常见原因是两个指数成倍数关系比如3和6这时候题目设计有误或者不是共模套路。工具里这个模块的输入槽比别的多两个填的时候容易对错位。我习惯先把e1、e2填上再填c1、c2然后在心里默念一遍对应关系填反了输出就是乱码。这里还容易和「共享素数」搞混后者是两个不同的n共用同一个因子p走的是模数分解路线输入方式完全不同。4.4 Wiener攻击连分数展开的终止条件d偏小时可以从公钥n、e的连分数展开中还原k/d的关系这是Wiener攻击的核心。它的适用边界有明确定论d n^0.25。工具在实现上就是做连续的连分数展开每一步算出一个候选d然后验证(e*d-1)能否被phi(n)整除。手动判断该不该跑Wiener前我会先看一眼位的分布from math import log2 n_bits int(log2(n)) e_bits int(log2(e)) print(n bits:, n_bits) print(e bits:, e_bits)逻辑说明如果e的位数接近n的位数比如n是1024位、e也接近1024位同时题目没有提供其他线索d很可能是小的值得跑Wiener。参数说明这只是个粗略预判不能替代实际验证。判断这个的目的是避免在d根本不小时白等因为连分数展开在d较大时会展开几十上百项每项都要做大数验算耗时随展开深度指数上升。工具里通常有「连分数深度」参数默认40项。遇到没跑出来的情况可以调到80但如果到了80还是没结果大概率题目根本不是Wiener攻击硬跑不如回到第2章的判断流程重新归类。这个模块还经常跟低加密指数混淆因为它们的适用场景都可以是e偏大区别在于明文和密文的相对大小完全不一样。我的经验是题目直接把n、e、c三个大整数文本丢给你的时候优先试Wiener题目给了多个文件、明显有冗余结构的时候优先找共模或共享素数。5. RSA工具避坑手册五类翻车现场与排查顺序工具本身不会帮你判断思路对不对它只负责执行你选择的攻击。这一章列出的五个坑我都实际踩过每一条按「现象、原因、解决」的顺序讲清方便你在翻了车之后按这个顺序排查。5.1 现象n、e、c都填对了输出却一直是乱码最诡异的翻车现场是参数肉眼检查完全没问题明文整数也对转成字符串却是乱码。原因多半不在工具而在输入的进制格式上。题目给的十六进制字符串以0x开头时如果工具的输入框默认按十进制解析0x9f...会被当成十进制的整数整个c的值差了数量级。解决进工具前统一完成进制转换。我习惯在填参数前先跑一段脚本强制打印三个值的十六进制和十进制两个形态再核对题目原始串。还有一个隐藏点PEM文件里解析出来的n有些工具会把前导00字节去掉而密文解码时又要求补回前导字节。如果明文开头少了一个字节把long_to_bytes(m)的结果补上\x00再转字符串就对了。这一点在公钥带前缀、密文长度刚好等于n的字节长度时特别容易踩到。5.2 现象自动检测把它当成Wiener攻击跑了十分钟不结束工具自动检测的顺序如果先跑Wiener题目又不是这个套路运行时间会非常难看。原因在于Wiener攻击的连分数展开没有前置校验只要e比n小一点它就会一路展开下去每一段都尝试验算。验算本质上要做模幂运算次数多了以后慢得离谱。解决在自动检测跑超过30秒时直接中断切到手动分类。先看n能不能试除再看e是不是过小这两个判断都是一眼能定的事。只有e位数接近n位数时才让Wiener跑完整流程。另外一个实操技巧把--mode auto改成--mode factor先跑分解类攻击这类攻击通常会先做小质数试除和费马分解一旦n有结构性弱点十几秒内就能给出结论远比干等Wiener划算。5.3 现象十六进制串带0x前缀工具却按十六进制解析失败命令行工具和图形工具对0x前缀的宽容度不一样。某些版本解析参数时刻不认0x只认纯十六进制数字导致填进去少了一个字母解析错位。原因在于参数解析库里用的是int(x, 16)而不是int(x, 0)前者遇到0x前缀直接抛异常。解决把所有带0x的字符串在脚本里做一次清理raw 0x9f2c... c int(raw.replace(0x, ), 16)逻辑说明先把前缀去掉再强制按十六进制转整数。参数说明这里不要用int(raw, 0)因为如果raw是纯数字串不带前缀int(raw, 0)会默认当十进制仍然出错。统一改成先替换前缀再指定进制和工具的解析逻辑保持一致。批量处理时也顺手把大写A-F全部转成小写避免个别解析器对小写字母敏感。5.4 现象公钥文件解析报错提示不是PEM格式PEM公钥以-----BEGIN PUBLIC KEY-----开头但很多时候题目给的不是标准PEM而是DER二进制格式或者PEM文件被base64换行符破坏过。RSA.import_key遇到DER时直接抛出异常。解决先判断真实格式再决定走哪条解析路径file public.key xxd public.key | head -10逻辑说明file输出会标明是PEM文本还是DER数据。xxd查看头部字节DER二进制通常以30 82开头PEM文本则是可读的ASCII字符串。参数说明如果确定是DER解析方式改为RSA.import_key(open(public.key, rb).read())大多数密码学库会自动识别DER如果还是失败可能是文件头部有额外字节先tail -c 2跳过多余字节再试。我见过一种特殊情况工具把私钥文件的注释行误判成公钥内容因为文件开头是-----BEGIN RSA PRIVATE KEY-----工具直接拒绝执行。这时要用openssl先单独提取公钥部分再喂给工具。5.5 现象攻击成功却被杀软或系统安全策略拦截工具目录下如果包含可执行文件有些平台的安全策略会自动隔离表现是双击没反应或运行到一半进程消失。原因不是工具本身有问题而是压缩包里的exe没有签名被系统当成了未受信程序。解决在解压目录里右键查看文件属性如果有「解除锁定」按钮勾选确认后再运行。Linux环境则检查文件执行权限补上chmod x再启动。这一步属于环境问题不代表工具功能有问题。我在某公司训练机上也遇到过类似情况当时顺手把目录加入了杀软白名单才跑通后来比赛用的电脑没有这么严格的限制直接解压就能用。优先确认依赖库齐全、参数格式正确最后再考虑系统拦截的因素。6. 进阶技巧把工具挂进自己的批处理流水线工具单次执行只能处理一道题但比赛训练经常需要批量验证几十道历史题目。我的做法是写一层薄薄的批处理脚本把工具当成一个命令行子进程来调用按目录组织每道题的参数。#!/usr/bin/env bash for d in ./cases/*/; do id$(basename $d) n$(python3 parse_params.py $d n) e$(python3 parse_params.py $d e) c$(python3 parse_params.py $d c) echo 正在处理 $id python3 xuan_yu_rsa.py -n $n -e $e -c $c --mode auto $d/result.txt done逻辑说明外层循环遍历cases目录下每个子目录子目录里放着一道题目的原始文件和参数。parse_params.py是从第3章的提取脚本演化出来的按题目目录自动读PEM或hex生成参数。每次执行结果写入该题目录的result.txt方便对照人工复盘。参数说明--mode auto可以在大规模扫描时统一用遇到失败目录我会再单拎出来手动指定模式重跑不在批量脚本里做二次尝试。另外我常用它配合SageMath做二次验证。工具跑出d或者分解出的p、q之后我会把这些值丢进SageMath里重算一遍私钥推导和签名验证确认工具结果不是偶然碰撞。这一步在自动检测成功时尤其值得做因为自动模式偶尔会在边界条件下给出错误候选解。比如费马分解循环里如果p、q差距过大它可能返回的是一组平方因子而不是真实质数分解。验证方法很简单把p和q乘回去看是否严格等于n再把解密结果加密回来看是否等于c。这两步能过滤掉九成以上的误报。回到开头那句话——CTF打RSA考的是识别套路的速度不是手写脚本的耐心。把这个工具当作第一手探测器快速扫完常规攻击面再针对未命中题目用符号计算工具做深挖是我现在习惯的节奏。工具解决80%的常规题剩下20%交给更自由的数学工具两者配合工作效率高得多。希望帮到你。本文还有配套的精品资源点击获取