1. 从一道经典密码学题目说起RSA脚本环境到底要配什么但凡接触过CTF竞赛里Crypto方向的人大概率都绕不开RSA这个坎。RSA作为非对称加密的鼻祖级算法在CTF题目里出现的频率极高而OldDriver这道题又是很多人在学习RSA脚本编写时遇到的一个典型关卡。它的核心特点在于题目给出的参数不是常规的完整RSA参数而是缺少了某些关键值需要你自己写脚本来推导和还原。很多新手第一次碰到这类题目时最大的障碍其实不是数学原理而是环境配置。Python版本选哪个、需要装哪些库、gmpy2怎么编译安装、sage要不要上、PyCryptodome和Crypto有什么区别——这些问题看起来琐碎但每一个都能卡住你半天。我自己当初配环境的时候光是gmpy2在Windows上的安装就折腾了整整一个下午各种报错轮番上阵最后发现是缺少C编译工具链。所以这篇内容我打算把RSA脚本环境配置这件事彻底讲透。从Python环境的选择、核心数学库的安装、到实际写一个能跑通OldDriver类题目的完整脚本再到常见报错的排查思路全部按我实际踩过的路来写。不管你是刚入门CTF的萌新还是想把自己环境整理得更规范的老手应该都能从里面找到有用的东西。核心关键词先摆出来RSA脚本、Python环境、gmpy2、PyCryptodome、CTF Crypto、大整数运算、模逆运算、因式分解。这些词后面会反复出现每一个我都会展开讲清楚它在RSA脚本里扮演什么角色。2. 环境整体设计与工具选型思路2.1 为什么Python是RSA脚本的首选语言写RSA脚本语言选择其实没太多悬念。Python几乎是CTF Crypto方向的默认语言原因很直接内置大整数支持。Python的int类型天然支持任意精度运算你写2**1024它就直接给你算出来不需要像C或Java那样引入BigInteger之类的库。RSA里面动辄1024位、2048位的模幂运算用Python写就是一行pow(c, d, n)的事。另一个原因是Python的密码学生态足够丰富。PyCryptodome提供了RSA密钥解析、加解密、签名验证等全套功能gmpy2提供了高性能的大整数运算和数论函数sympy可以做符号计算和因式分解sagemath更是把几乎所有数论工具都打包好了。这些库组合起来基本能覆盖CTF中RSA题目的所有需求场景。不过Python也有版本选择的坑。强烈建议用Python 3.8到3.11之间的版本不要用太老的3.6/3.7也不要用最新的3.13。原因在于gmpy2和PyCryptodome这些库对Python版本有兼容性要求太老的版本有些新特性不支持太新的版本可能还没有预编译的wheel包装起来要自己编译非常痛苦。我自己现在稳定用的是3.10各种库的wheel包都很齐全pip install基本一把过。2.2 核心库的分工与选型对比RSA脚本涉及的核心库不多但每个都有明确的职责。我把它们的分工整理成一张表方便你理解为什么要装这些而不是别的库名称核心职责典型使用场景是否必装gmpy2高性能大整数运算、数论函数模逆、开方、素性检测、next_prime强烈推荐PyCryptodome密码学算法实现、密钥解析解析PEM/DER密钥、RSA加解密推荐sympy符号计算、因式分解小素数分解、方程求解可选factordb-pycli在线分解数据库查询查询已知合数的因子可选requestsHTTP请求与在线API交互可选这里重点说一下gmpy2和PyCryptodome这两个。gmpy2是RSA脚本的性能核心它底层用GMP库实现大整数运算速度比Python原生int快几倍到几十倍。CTF题目里经常需要暴力枚举或者大量模幂运算用gmpy2能省很多时间。它提供的invert()做模逆、iroot()做整数开方、is_prime()做素性检测都是RSA脚本里的高频函数。PyCryptodome则是功能核心。OldDriver这类题目经常给你一个公钥文件或者一段密文你需要用PyCryptodome来解析密钥结构、提取n和e。注意这个库的导入名是Crypto而不是PyCryptodome而且它和老的pycrypto库有冲突装之前一定要先卸载pycrypto否则会出现各种诡异的导入错误。2.3 虚拟环境的必要性我见过太多人把所有库都装在系统Python里结果不同项目之间版本冲突最后环境彻底崩掉。用虚拟环境是必须的这不是什么高级技巧而是基本的工程习惯。venv是Python自带的不需要额外安装用起来最简单python -m venv rsa_env # Windows rsa_env\Scripts\activate # Linux/Mac source rsa_env/bin/activate激活之后你所有的pip install都只影响这个虚拟环境不会污染系统Python。项目做完直接删掉整个文件夹就行干净利落。如果你经常做CTF可以专门建一个crypto_env把所有密码学相关的库都装在里面每次做题激活一下就行。提示虚拟环境激活后命令行前面会出现(rsa_env)的标识看到这个标识才说明激活成功。如果没看到后面装的库可能装到系统Python里去了。3. 核心库安装实操与踩坑记录3.1 gmpy2安装Windows和Linux的不同姿势gmpy2的安装是新手最容易翻车的地方因为它是C扩展库需要编译工具链。不同操作系统的处理方式完全不同。Windows下的安装现在gmpy2在PyPI上有预编译的wheel包了正常情况下直接pip install gmpy2就能装上。但如果你用的是比较新的Python版本可能没有对应的wheelpip会尝试从源码编译然后报错说找不到mpc.h、gmp.h这些头文件。遇到这种情况最省事的办法是去下载对应你Python版本和系统架构的whl文件然后本地安装pip install gmpy2-2.1.5-cp310-cp310-win_amd64.whlwhl文件名里的cp310表示Python 3.10win_amd64表示64位Windows。下载的时候一定要对准自己的版本装错了会报not a supported wheel on this platform。Linux下的安装Linux下反而更简单因为可以直接用系统包管理器装依赖然后pip编译安装# Ubuntu/Debian sudo apt-get install libgmp-dev libmpfr-dev libmpc-dev pip install gmpy2 # CentOS/RHEL sudo yum install gmp-devel mpfr-devel libmpc-devel pip install gmpy2装完验证一下import gmpy2 print(gmpy2.version()) print(gmpy2.mpz(2)**100)如果这两行能正常输出说明gmpy2装好了。mpz是gmpy2的大整数类型和Python的int可以互相转换但运算速度更快。3.2 PyCryptodome安装与pycrypto冲突处理PyCryptodome的安装本身很简单pip install pycryptodome就行。但它的坑在于和pycrypto的命名冲突。这两个库的导入名都是Crypto如果你系统里同时装了pycrypto和pycryptodomePython会优先导入先装的那个导致你调用某些函数时报AttributeError。处理办法是先检查有没有装pycryptopip list | grep -i crypto如果看到pycrypto先卸载它pip uninstall pycrypto pip install pycryptodome装完之后验证from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime print(long_to_bytes(0x68656c6c6f))long_to_bytes和bytes_to_long是RSA脚本里最常用的两个转换函数前者把整数转成字节串后者反过来。CTF题目给的密文经常是整数形式你需要转成字节才能看到明文。3.3 其他辅助库的安装sympy和requests的安装没什么坑直接pip就行pip install sympy requestssympy主要用来做小规模的因式分解和方程求解比如题目给的n比较小几百位以内可以用sympy.factorint(n)直接分解。但如果n是1024位以上的大数sympy就无能为力了得靠factordb或者更高级的算法。factordb-pycli是一个查询在线分解数据库的命令行工具安装pip install factordb-pycli用的时候直接factordb 数字就能查询这个数是否已经被分解过。CTF题目里很多n其实是已知的合数factordb里直接有因子查一下就能秒解。注意factordb是国外服务访问可能不稳定。如果查不出来不要死磕换其他思路。3.4 环境验证脚本装完所有库之后我习惯跑一个验证脚本确认环境没问题import gmpy2 from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime, inverse import sympy # 测试gmpy2大整数运算 a gmpy2.mpz(2)**512 print(gmpy2 ok:, a.bit_length()) # 测试模逆 e 65537 phi (getPrime(256)-1) * (getPrime(256)-1) d inverse(e, phi) print(inverse ok:, (e * d) % phi 1) # 测试字节转换 print(convert ok:, long_to_bytes(bytes_to_long(btest)) btest) # 测试sympy分解 print(sympy ok:, sympy.factorint(123456789))这个脚本能跑通说明你的RSA脚本环境基本就绪了。任何一步报错就针对性地去解决那个库的问题。4. OldDriver类题目的脚本编写与核心环节4.1 这类题目的典型结构分析OldDriver这类RSA题目核心特征在于参数不完整。常规RSA题目会给你n、e、c三个值你只需要分解n得到p和q然后算d解密就行。但OldDriver类题目往往会隐藏某些参数或者给出一些额外的关系式需要你自己推导。常见的变体包括只给e和c不给n给n和e但n无法直接分解需要通过其他信息推导给出多个密文和对应的模数需要用中国剩余定理给出p和q的部分比特需要做Coppersmith攻击。这些变体的共同点是你需要写脚本来自动化推导过程而不是手算。以最经典的一种为例题目给了一个公钥文件public.key和一个密文文件公钥文件里包含n和e但n是一个大合数。你需要先解析公钥提取n和e然后尝试分解n最后解密。4.2 公钥解析与参数提取解析公钥文件用PyCryptodome的RSA模块from Crypto.PublicKey import RSA with open(public.key, rb) as f: key RSA.import_key(f.read()) n key.n e key.e print(n , n) print(e , e) print(n bits , n.bit_length())RSA.import_key能自动识别PEM和DER格式非常方便。提取出来的n和e就是后续所有运算的基础。如果题目给的不是公钥文件而是直接给的n和e的十六进制字符串那就更简单n int(0x..., 16) e int(0x..., 16)4.3 因式分解的多种策略拿到n之后核心任务就是分解它。根据n的特点有不同的策略策略一直接查factordb。如果n是已知的合数factordb里直接有因子。这是最快的方法优先尝试。策略二小素数试除。如果n的因子比较小比如小于10^6可以用试除法import gmpy2 def small_factor(n, limit1000000): for i in range(2, limit): if n % i 0: return i, n // i return None, None策略三费马分解。如果p和q非常接近可以用费马分解法import gmpy2 def fermat_factor(n): a gmpy2.isqrt(n) 1 while True: b2 a * a - n b gmpy2.isqrt(b2) if b * b b2: return a - b, a b a 1费马分解的原理是如果n p*q且p和q接近那么n可以写成a^2 - b^2的形式其中a (pq)/2b (p-q)/2。当p和q差距很小时a从sqrt(n)开始往上找很快就能找到。策略四Pollards rho。对于有较小因子的nPollards rho算法效率很高import gmpy2 import random def pollard_rho(n): if n % 2 0: return 2 while True: x random.randint(2, n-1) y x c random.randint(1, n-1) d 1 while d 1: x (x*x c) % n y (y*y c) % n y (y*y c) % n d gmpy2.gcd(abs(x-y), n) if d ! n: return d策略五已知p或q的高位/低位。如果题目给了p的高位比特可以用Coppersmith方法恢复完整的p。这需要sagemath后面单独讲。4.4 解密与明文还原分解出p和q之后解密就是标准流程from Crypto.Util.number import long_to_bytes, inverse phi (p - 1) * (q - 1) d inverse(e, phi) m pow(c, d, n) plaintext long_to_bytes(m) print(plaintext)这里有几个细节要注意。第一inverse函数在Python 3.8之后是内置的但PyCryptodome也提供了Crypto.Util.number.inverse两者功能一样。第二如果e和phi不互素即gcd(e, phi) ! 1那么d不存在需要用其他方法比如对e的每个因子分别求解再组合。第三long_to_bytes转换出来的字节串可能包含不可打印字符如果明文是flag格式通常能看到flag{...}这样的结构。4.5 完整脚本示例把上面的步骤串起来一个完整的OldDriver类题目求解脚本大概长这样import gmpy2 from Crypto.PublicKey import RSA from Crypto.Util.number import long_to_bytes, inverse # 1. 解析公钥 with open(public.key, rb) as f: key RSA.import_key(f.read()) n, e key.n, key.e print(f[*] n {n}) print(f[*] e {e}) print(f[*] n bits {n.bit_length()}) # 2. 尝试分解n def try_factor(n): # 先试小素数 for i in range(2, 100000): if n % i 0: return i, n // i # 费马分解 a gmpy2.isqrt(n) 1 for _ in range(1000000): b2 a * a - n b gmpy2.isqrt(b2) if b * b b2: return int(a - b), int(a b) a 1 return None, None p, q try_factor(n) if p is None: print([!] 分解失败需要其他方法) exit(1) print(f[*] p {p}) print(f[*] q {q}) assert p * q n # 3. 解密 phi (p - 1) * (q - 1) d inverse(e, phi) c int(open(cipher.txt).read().strip()) m pow(c, d, n) print(f[*] plaintext {long_to_bytes(m)})这个脚本涵盖了从公钥解析到明文还原的完整流程你可以直接拿去改改用。5. 常见报错与排查技巧实录5.1 环境类报错速查表报错信息原因解决方法ModuleNotFoundError: No module named gmpy2没装gmpy2或装到了别的环境确认虚拟环境激活重新pip installImportError: cannot import name long_to_bytes装的是pycrypto不是pycryptodome卸载pycrypto装pycryptodomeValueError: not a supported wheelwhl文件版本不匹配下载对应Python版本和架构的whlgmpy2.mpz object has no attribute bit_length用了gmpy2类型但调用了int方法用int(x).bit_length()或x.num_digits(2)ZeroDivisionError模逆时gcd不为1检查e和phi是否互素5.2 分解失败怎么办分解失败是RSA题目里最常见的问题。排查思路是这样的首先确认n的位数。如果n小于256位可以尝试用sympy的factorint或者yafu工具直接分解。如果n在256到512位之间可以试试Pollards rho或者Pollards p-1。如果n大于512位基本只能靠题目给的额外信息比如p的高位、p和q的关系来攻击。其次检查题目有没有给其他提示。很多OldDriver类题目会在附件里藏一些信息比如一个hint.txt或者密文文件里除了c还有别的值。仔细看题目描述和附件内容往往能找到突破口。最后考虑是不是需要上sage。sagemath提供了很多高级攻击方法比如Coppersmith、LLL格基约减、小根攻击等。如果题目涉及这些Python原生库搞不定必须用sage。5.3 实操心得几个容易忽略的细节第一个细节e的值不一定是65537。虽然绝大多数题目e都是65537但也有题目用3、17、或者其他值。e3的时候要特别注意小明文攻击如果m^3 n直接对c开三次方就能得到m。第二个细节密文可能是十六进制字符串。有些题目给的c是0x...格式你需要先int(c, 16)转成整数再解密。如果直接当十进制处理会得到错误结果。第三个细节明文可能还需要进一步处理。有时候解密出来的字节串不是直接的flag而是经过了一层编码比如base64、hex。看到可打印字符但不像flag的时候试试各种解码。第四个细节多组密文要用中国剩余定理。如果题目给了同一明文用不同模数加密的多个密文且e比较小可以用CRT组合from sympy.ntheory.modular import crt def rsa_crt_attack(c_list, n_list, e): m_e, n crt(n_list, c_list) m gmpy2.iroot(m_e, e)[0] return int(m)这个攻击的前提是m^e product(n_i)否则CRT出来的结果不是真正的m^e。6. 进阶工具链与效率提升6.1 sagemath的安装与使用场景sagemath是CTF Crypto的终极武器它把Python、Maxima、GAP、Singular等一堆数学软件整合在一起提供了大量高级数论函数。安装sage在Linux下可以用aptWindows下建议用WSL或者直接装Linux虚拟机。sage里常用的RSA攻击函数包括factor()做因式分解、inverse_mod()做模逆、discrete_log()做离散对数、small_roots()做Coppersmith小根攻击。这些函数在Python原生环境里要么没有要么实现起来很麻烦。不过sage的安装包很大几个GB启动也比较慢。我的建议是日常简单题目用Pythongmpy2就够了遇到需要Coppersmith或者格基约减的题目再上sage。不用为了一个简单题目去开sage。6.2 常用脚本模板整理做了足够多的RSA题目之后你会发现很多步骤是重复的。我习惯把常用功能封装成函数放在一个rsa_utils.py里每次做题直接import# rsa_utils.py import gmpy2 from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse def parse_pubkey(path): from Crypto.PublicKey import RSA with open(path, rb) as f: key RSA.import_key(f.read()) return key.n, key.e def decrypt(c, e, p, q): phi (p - 1) * (q - 1) d inverse(e, phi) m pow(c, d, p * q) return long_to_bytes(m) def fermat(n, max_iter1000000): a gmpy2.isqrt(n) 1 for _ in range(max_iter): b2 a * a - n b gmpy2.isqrt(b2) if b * b b2: return int(a - b), int(a b) a 1 return None, None这样每次做题只需要写题目特有的逻辑通用部分直接调用。6.3 在线资源的合理利用factordb是最常用的在线资源但不要过度依赖。有些题目故意用factordb查不到的n逼你用其他方法。另外访问factordb可能不稳定建议本地也准备一套分解工具作为备份。yafu是一个本地的大数分解工具支持多种算法自动切换对于中等规模的n100-300位效果不错。安装之后命令行直接yafu factor(n)就行。提示在线资源查询时注意不要泄露题目信息尤其是比赛期间。有些比赛禁止使用在线工具用之前先看规则。7. 我个人在实际操作中的几点体会配环境这件事看起来是体力活但其实很考验你对工具链的理解。我见过太多人卡在gmpy2安装上就放弃了Crypto方向其实只要搞清楚wheel包的命名规则和虚拟环境的作用这些问题都不难解决。另外一个体会是不要追求一次装完所有库。我一开始也是想把sage、yafu、各种工具全装上结果环境冲突不断。后来改成按需安装做简单题目就用Pythongmpy2PyCryptodome三件套遇到需要sage的题目再单独开一个环境。这样每个环境都很干净出问题也好排查。最后分享一个小技巧把常用的RSA脚本模板和工具函数整理成一个自己的代码库每次做题直接复制粘贴改参数。做得多了你会发现CTF里的RSA题目翻来覆去就是那些套路真正需要创新的地方不多。把基础打牢把工具链理顺剩下的就是熟练度的问题了。