☰
rsarsa CTF入门题解:RSA解密原理与Python实现
2026/10/15 6:01:58 网站建设 项目流程

1. 拿到题目先看透:rsarsa 到底在考什么

1.1 题目文件和有效信息

这次要记录的是一道名叫 rsarsa 的 CTF 入门题。题目本身并不复杂,属于看到参数就能直接动手的类型,但正因为它太典型,反而值得写一篇完整的 Writeup:从 RSA 原理到解密脚本,再到 Writeup 自己应该怎么写,一条线捋清楚。我在做这道题的时候还用了 AI 参与整理思路和检查代码,所以标题后面挂了 by AI。不过先说清楚,AI 不是主角,真正把题解出来的还是数学和边界检查。这篇文章适合刚接触 CTF 的读者,也适合需要快速复习 RSA 基础的人。

题目压缩包解压后通常就两个文件:一个加密脚本,一个输出文件。加密脚本里做了这样几件事:生成两个大素数 p 和 q,计算 n = p * q,选择一个常用的公钥指数 e,然后用 RSA 加密一段文本,最后把参数和密文打印出来。rsarsa 这道题比较直接,脚本会把 p、q、e、c(密文)全部原样输出。也就是说,除了私钥 d 没有直接给之外,其他东西基本算是“全裸”了。

1.2 先判断攻击面而不是急着解密

拿到题第一件事不是写脚本,而是先看题目给了一组什么样的参数。不同参数组合对应的解法完全不同,这一步判断错了,后面所有工作都会白费。我自己习惯按下面这个顺序快速过一遍:

题目给了什么优先考虑的方法难度
p, q, e, c 全给直接求 φ(n),再求 d,最后解出 m入门
只给 n, e, c尝试分解 n;如果 n 很短可以用工具跑中等
只给 n, e, c 且 e 很小低加密指数攻击,直接开 e 次方中等
多个用户共用同一个 n共模攻击进阶
e 很大或 d 很小Wiener 攻击等私钥恢复手段进阶

rsarsa 明显落在第一行。它的考点不是让你去分解大整数,而是考察你是否知道 RSA 解密的最基本流程。说得再直白一点,出题人把私钥的所有组成部分都摆在你面前了,只差一个求模逆元的小操作。

2. RSA 解密必要前置知识:不搞懂原理拿到代码也白搭

2.1 从公钥到私钥的数学链条

RSA 的核心公式没有想象中那么吓人。加密过程是:

c = m^e mod n

解密过程是:

m = c^d mod n

其中 m 是明文,c 是密文,e 是公钥指数,n 是模数,d 是私钥指数。如果只看这两个公式,会发现解密和加密长得非常像,区别只在于指数不同。这个思路可以类比成一把锁:加密是用公钥把消息锁进箱子里,解密是用私钥把锁打开。没有私钥,别人只能看到箱子上乱七八糟的密文,拿不到里面的内容。

那 d 从哪里来?d 不是随机选的,它必须满足一个条件:

e * d ≡ 1 (mod φ(n))

这里的 φ(n) 是欧拉函数。对于 n = p * q 这种由两个素数乘积构成的数,φ(n) = (p - 1) * (q - 1)。所以只要知道 p 和 q,就能算出 φ(n),再用扩展欧几里得算法求出 d。这就是为什么题目一旦把 p 和 q 直接给你,这道题就瞬间变成了“套公式”的算术题。

2.2 为什么有 p 和 q 就等于拿到钥匙

有人可能会问:RSA 不是号称安全吗?为什么知道 p 和 q 就能破解?问题出在 n 的分解难度上。RSA 的安全性建立在“已知 n 很难分解出 p 和 q”这个前提上。如果 n 是一般人用铅笔算不动的 2048 位大整数,那在没有 p 和 q 的情况下,想靠暴力尝试恢复私钥几乎不可能。但题目直接把 p 和 q 打印出来,等于把守卫引以为傲的锁芯图纸公开了,后续所有计算都变成普通乘法。

我在刚开始学 RSA 的时候也犯过一个理解上的错误:以为只要有 n 和 e 就能算出 d。实际上没有 φ(n) 拿不到 d,而没有 p 和 q 就拿不到 φ(n)。所以整条链子是这样的:

n = p * q → φ(n) = (p - 1) * (q - 1) → d = e^{-1} mod φ(n) → m = c^d mod n

缺了任何一环,后面的步骤都走不下去。rsarsa 最大的善意就是帮你把第一环直接补好了。

2.3 Python 里的大整数运算和模逆

用 Python 写 RSA 解密脚本非常省心,因为 Python 的整数类型不限制长度。C 语言里可能要考虑溢出,Java 要拿 BigInteger 反复调用 API,Python 只需要直接写整数运算。真正要注意的是求模逆元。Python 3.8 及以上版本可以直接用:

d = pow(e, -1, phi)

如果运行环境是旧版本,会直接报错。这时候需要自己实现扩展欧几里得算法,或者使用 gmpy2 库里的invert函数。我自己实测下来,pow(e, -1, phi)是最简洁的方案,但如果你不确定平台 Python 版本,写成兼容旧版本的形式更稳妥。后面我会在实操脚本里给出两个版本,方便不同环境的读者直接抄作业。

3. 实操:从零手写解密脚本

3.1 先给出一个能跑通的小例子

真实题目里给的是几十位甚至上百位的大整数,但对一个入门级 Writeup 来说,直接堆大数会让重心跑偏。所以我先用教科书里最经典的迷你参数演示流程,保证你能完完整整地跑一遍,再说明如何替换成原题的大参数。

我用的示例数据是:

p = 61 q = 53 e = 17 c = 2790

这个例子来自非常经典的 RSA 教学演示:n = 61 * 53 = 3233,φ(n) = 60 * 52 = 3120,私钥指数 d = 2753。加密明文 65,得到密文 2790。下面我们的任务就是给定 p、q、e、c,反过来算出 65。

3.2 三步解出明文

完整脚本如下:

# 方法一:Python 3.8+ 写法 from math import gcd p = 61 q = 53 e = 17 c = 2790 n = p * q phi = (p - 1) * (q - 1) # 检查 e 和 phi 是否互质 if gcd(e, phi) != 1: raise ValueError("e 和 phi 不互质,无法求逆元") d = pow(e, -1, phi) m = pow(c, d, n) print("n =", n) print("phi =", phi) print("d =", d) print("m =", m) # 转成 ASCII byte_len = (m.bit_length() + 7) // 8 plaintext = m.to_bytes(byte_len, "big") print(plaintext)

运行结果:

n = 3233 phi = 3120 d = 2753 m = 65 b'A'

这里最关键的一行是pow(c, d, n)。Python 内置的这个三元pow会直接计算c的d次方并对n取模。虽然你也可以写成pow(c, d) % n,但pow(c, d, n)的运算效率高得多,尤其在处理几百位大整数时,后一种写法可能会慢到让你怀疑人生。

如果你的 Python 版本小于 3.8,pow(e, -1, phi)不可用。我提供一个兼容写法:

def egcd(a, b): if b == 0: return a, 1, 0 g, x1, y1 = egcd(b, a % b) x = y1 y = x1 - (a // b) * y1 return g, x, y def modinv(a, m): g, x, _ = egcd(a, m) if g != 1: raise ValueError("No inverse exists") return x % m d = modinv(e, phi)

这段代码本质上是扩展欧几里得算法。它做的事情和pow(e, -1, phi)是一样的,只是不依赖新版 Python。

3.3 从整数到字符串的编码转换

算出 m 只是拿到了整数形式的明文。CTF 题目的 flag 通常是可读字符串,所以还需要把整数转成字节序列。这里我最常用的写法是:

byte_len = (m.bit_length() + 7) // 8 flag = m.to_bytes(byte_len, "big") print(flag)

为什么长度要这样算?一个字节是 8 位,m.bit_length()返回 m 用二进制表示需要多少位,除以 8 向上取整就是需要的字节数。+7是向上取整的经典写法。如果不加 7,遇到 bit 数正好不是 8 的倍数时,转换结果会丢掉最高位的若干 bit,轻则乱码,重则直接缺字节。

如果你用真实的大参数跑完,输出的应该是b'flag{...}'这样的字节串。如果看到开头有个b,不要慌,那是 Python bytes 类型的显示形式。实际题目提交时去掉b''即可。

3.4 写脚本时最容易踩的四个坑

第一个坑是顺手写了pow(c, d) % n,虽然结果对,但对大数来说性能极差。第二个坑是忽略 e 和 φ(n) 的互质检查,偶发情况下 e 和 phi 不互质会导致 modinv 报错。第三个坑是转字符串时误用了小端字节序little,导致字符顺序颠倒。第四个坑是没有把脚本里的p,q,e,c从示例值替换成题目的真实值,导致解出来的内容完全不对。

我自己在实际练习中踩得最狠的是第三个坑。有一次我解出一个十进制的 m,转 bytes 后全是乱码,后来发现是大小端写反了。RSA 中明文转密文用的是大端表示,所以to_bytes的第二个参数应该写"big"。这一点在写 Writeup 时一定要单独标注出来,因为对新手来说是很容易被忽略的细节。

4. 和 AI 一起写 Writeup 的协作复盘

4.1 AI 在解题和写作中起到的作用

标题里既然写了 by AI,那我就老老实实复盘一下 AI 在这里面帮了什么忙。说实话,rsarsa 这道题本身难度很低,手算都能做出来,并不需要 AI 来“解题”。真正让 AI 发挥价值的是两个环节:第一,检查边界情况和代码兼容性;第二,把零散的解题记录整理成结构清晰的 Writeup。

我一开始把解密脚本丢给 AI 做代码审查,它会主动提醒我检查 e 是否与 φ(n) 互质,还会告诉我pow(e, -1, phi)在 Python 3.8 之前不可用。这些点确实提高了脚本的健壮性。在写文章时,我也会让 AI 先根据关键词列一个提纲,再把我手写的解题过程填充进去。可以说,AI 更像一个“思路校对员”和“文本排版助手”,而不是解题主角。

4.2 必须人工复核的几个细节

AI 生成的内容看起来越流畅,越要警惕几个坑。尤其是这种数学类 Writeup,公式和代码必须逐条核对。我自己总结了一个检查清单:

  • 所有变量名是否一致。AI 有时候会把 p 和 q 写成 a 和 b,把 c 写成 cipher,概念上没错,但读者复制代码会报错。
  • 公式里的模符号有没有丢失。很明显的一个点:如果e * d ≡ 1 (mod φ(n))被误写成了e * d = 1 / φ(n),整段文章就直接废了。
  • 示例参数能不能真的跑通。我会把文章里的示例代码复制到本地跑一遍,确保输出和文章一致。这一步不能偷懒。
  • 有没有过度解释。AI 擅长一本正经地补充很多背景知识,但 Writeup 最核心的价值是说清楚“题目考什么、我怎么解出来的”,而不是把 RSA 的历史从头讲一遍。

我建议任何人在使用 AI 协作写技术文章之后,都自己做一次“本地复现”。文章里贴的代码如果作者自己都没跑通过,那这篇文章对读者的帮助会大打折扣。

4.3 协作过程里我觉得舒服的工作流

这次写 rsarsa 时我用的流程可以分享给参考:先自己做完题,记录关键步骤;再用自然语言向 AI 描述题目给了哪些参数;然后让 AI 根据记录生成一版草稿;最后我手动替换成真实参数,跑通代码,再修缮文字。

这样做的好处是:AI 负责把重复的、格式化的工作消化掉,而解题逻辑和最终的判断权始终在我手里。如果你直接让 AI 从零“编”一篇 Writeup,它很可能会编出虚假的题目参数,甚至算出错误的 flag。记住一句话:AI 是加速器,不是方向盘。

5. 从 rsarsa 延伸出去的 RSA 攻击面

5.1 没有 p 和 q 时怎么办

很多题目不会像 rsarsa 这么客气。假设你只拿到 n、e、c,那第一个攻击方向就是尝试分解 n。如果 n 只有几十位,甚至可以用在线分解工具直接得到 p 和 q。如果 n 稍微大一点,可以用工具跑一下 Pollard's p-1 或椭圆曲线分解方法。需要注意的是,现在很多 CTF 平台会给 1024 位或 2048 位的 n,这种大小不能靠在线工具硬拆,必须要看出题人是否留下了破绽。

如果 e 非常小,比如 e = 3,而 m^3 小于 n,那密文直接开三次方就能还原明文。如果 m^3 大于 n 但不够大,可以考虑使用低加密指数广播攻击的变体。这些都是比基础 RSA 解密复杂一些的考点,但它们的根源还是同一个数学结构。把 rsarsa 的流程吃透了,再去看这些攻击方法的脚本,至少能看懂每一步在干什么。

5.2 一套可以通吃的解题模板

我把处理 RSA 题的通用流程写成一个模板,后面做同类题目时可以直接套:

  1. 列出所有已知变量:p, q, n, e, d, c, m 中你手里有哪几个。
  2. 判断缺口在哪:缺 d 就求 d,缺 p/q 就分解 n,缺 m 就用已知私钥解 m。
  3. 检查 e 和 φ(n) 的关系,确认逆元存在。
  4. 用 Python 写出最短的解密链路。
  5. 把整数明文转成字节串,确认编码方式。
  6. 在本地跑通后,回看题目描述,确认 flag 格式,再提交。

这个模板对绝大多数入门 RSA 题都有效。甚至很多中等题只是在这个基础上换了个攻击入口,核心链路不变。

5.3 工程向的小建议

最后聊一点工程习惯。解这类题时我建议把题目文件和脚本放到同一个文件夹里,参数单独写成一个配置文件或常量区。这样当你换一道题时,只需要改变量值,不用动算法逻辑。还有就是写 Writeup 时,代码块上面一定要清晰标注“这是完整脚本”还是“只展示核心片段”。如果只是片段,最好说明前后省略了什么。否则读者复制后跑不通,又要花时间找原因,体验很差。

我还会在脚本里加上assert做快速校验,比如assert n == p * q,避免因为参数复制错误导致白跑一趟。这种小习惯在入门阶段不明显,但等到接触真实多文件题目时,能省下不少定位问题的精力。

最后说一点个人体会

rsarsa 这道题本身没什么难度亮点,但它很适合做 RSA 入门的第一个完整案例。我在实际做它的时候,最大的收获反而是把“拿到题不要急着跑脚本,先列参数、再判断攻击面”这个流程内化成了习惯。后来遇到很多 RSA 变种题,都会下意识地先在纸上把变量关系写一遍,然后再碰键盘。

如果你是在跟着这篇文章第一次写 RSA 解密脚本,我建议你把示例代码跑通之后,再自己修改几个参数,比如换一个明文、换一组 p 和 q。亲手验证一遍之后,你就不会再觉得 RSA 是一个黑盒子了。关于 AI 参与写 Writeup,我的态度是:可以用,但一定要把最终解释权留给自己。一份合格的 Writeup,不是读起来顺,而是别人按照你的步骤,真的能把答案算出来。

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

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

立即咨询