RSA共模攻击原理与CTF实战:从贝祖等式到Python脚本实现

发布时间:2026/8/1 17:43:34

RSA共模攻击原理与CTF实战:从贝祖等式到Python脚本实现 1. 项目概述一次经典的RSA模数攻击实战复盘如果你玩过CTFCapture The Flag中的密码学题目尤其是RSA相关题型那么“Common Modulus Attack”共模攻击绝对是一个绕不开的经典考点。这次复盘的是来自“GXYCTF2019”的一道同名题目。它没有花哨的包装题目名就是攻击手法本身这往往意味着出题人想考察的就是你对这个攻击原理最纯粹的理解和实现能力。在实际的CTF比赛中RSA的共模攻击出现频率相当高因为它完美地揭示了密码学中的一个重要原则密钥的绝对独立性。简单来说同一份数据即使用两把不同的钥匙公钥分别加密只要它们共用同一把锁芯模数N并且在特定条件下攻击者就有可能在不拿到任何一把私钥的情况下还原出原始数据。这道题就是一个绝佳的练手场让我们能深入理解数学原理并亲手写出攻击脚本。无论你是刚接触密码学的萌新还是想巩固基础的老手通过拆解这道题都能对RSA的底层机制和常见脆弱点有更深刻的认识。2. 攻击原理深度拆解为什么共模是危险的在开始写代码之前我们必须吃透背后的数学原理。一知半解地套用脚本下次题目稍作变形就会束手无策。2.1 RSA加密过程回顾与问题场景设定首先我们快速回顾一下标准的RSA加密。假设Bob生成了他的RSA密钥对选取两个大质数p和q计算模数N p * q。计算欧拉函数φ(N) (p-1)*(q-1)。选取一个公钥指数e满足1 e φ(N)且gcd(e, φ(N)) 1即e与φ(N)互质。计算私钥指数d满足e * d ≡ 1 (mod φ(N))。当Alice想给Bob发送消息m在RSA中m是一个数字代表经过填充的明文时她使用Bob的公钥(N, e)进行加密c ≡ m^e (mod N)得到密文c。现在我们引入“共模攻击”的场景同一个模数N被生成了两次这是一个严重的错误。生成了两对不同的公钥(N, e1)和(N, e2)。最关键的是这两个公钥指数e1和e2必须是互质的即gcd(e1, e2) 1。攻击者我们拿到了这个公共的模数N以及两对公钥(N, e1),(N, e2)。更重要的是攻击者还拿到了同一明文消息m分别用这两对公钥加密后的密文c1和c2。c1 ≡ m^e1 (mod N)c2 ≡ m^e2 (mod N)我们的目标在不知道私钥d1或d2也不知道p和q的情况下恢复出明文m。2.2 核心数学武器扩展欧几里得算法与贝祖等式攻击的核心在于一个著名的数论定理——贝祖等式Bézout‘s identity。该定理指出对于两个互质的整数e1和e2必然存在两个整数s和t使得e1 * s e2 * t gcd(e1, e2) 1而寻找s和t的高效算法就是扩展欧几里得算法Extended Euclidean Algorithm。这个算法不仅能计算最大公约数还能同时计算出满足贝祖等式的系数s和t。现在把我们的密文c1和c2代入这个等式。我们知道c1 ≡ m^e1 (mod N)c2 ≡ m^e2 (mod N)如果我们计算(c1^s * c2^t) mod N会发生什么c1^s * c2^t ≡ (m^e1)^s * (m^e2)^t ≡ m^(e1*s e2*t) (mod N)根据贝祖等式e1*s e2*t 1。因此上式简化为c1^s * c2^t ≡ m^1 ≡ m (mod N)看明文m就这样被我们恢复出来了这就是共模攻击的全部数学精髓。它不依赖于分解大整数N只利用了公钥指数互质和同一明文被重复加密的条件。注意这里s或t很可能是一个负数。负数指数在模运算中意味着需要计算模逆元。例如如果s是负数那么c1^s mod N实际上应该计算为modinv(c1^(-s), N)其中modinv(a, N)是a在模N下的逆元。在实际编程中我们可以利用Python的pow函数特性或手动处理。2.3 与其它RSA攻击的关联与区分理解共模攻击有助于我们厘清RSA的威胁模型低加密指数攻击如e3当e很小且明文m满足m^e N时可以直接开方得到m。这与共模攻击无关。模数分解如果N被分解则RSA体系完全崩溃。共模攻击避免了直接分解N。共模攻击核心在于密钥管理失误重复使用N和数据管理失误同一明文用不同公钥加密。它告诉我们即使有多个公钥只要它们共享模数且加密了相同数据风险就极大。广播攻击Håstad‘s Broadcast Attack与共模攻击有些类似但场景不同。广播攻击是同一明文m用相同的公钥指数e但不同的模数N1, N2, ..., Nk加密当k e时可以利用中国剩余定理CRT攻击。切勿混淆。3. 题目实战解析从获取数据到编写攻击脚本理论清晰后我们进入实战。CTF题目通常会以多种形式给出数据常见的是提供一个文本文件或网络服务里面包含了PEM格式的公钥或直接给出了N, e的值以及密文。3.1 数据提取与格式处理假设我们从题目附件中得到了两个公钥文件pubkey1.pem,pubkey2.pem和两个密文文件cipher1.txt,cipher2.txt。第一步是解析这些文件。对于PEM格式的公钥我们可以使用Python的Crypto库现为pycryptodome来读取。from Crypto.PublicKey import RSA # 读取公钥1 with open(‘pubkey1.pem‘, ‘r‘) as f: key1 RSA.import_key(f.read()) N1 key1.n e1 key1.e # 读取公钥2 with open(‘pubkey2.pem‘, ‘r‘) as f: key2 RSA.import_key(f.read()) N2 key2.n e2 key2.e print(f“N1: {N1}“) print(f“e1: {e1}“) print(f“N2: {N2}“) print(f“e2: {e2}“)关键检查点立刻验证N1 N2。如果不相等那这就不是一道共模攻击题需要转换思路。如果相等我们记这个公共模数为N。接下来读取密文。密文可能是十六进制字符串、Base64编码或直接的十进制大整数。需要根据题目说明或常见格式进行转换。# 假设密文是十六进制字符串 with open(‘cipher1.txt‘, ‘r‘) as f: c1_hex f.read().strip() c1 int(c1_hex, 16) # 转换为整数 with open(‘cipher2.txt‘, ‘r‘) as f: c2_hex f.read().strip() c2 int(c2_hex, 16) print(f“c1: {c1}“) print(f“c2: {c2}“)3.2 攻击脚本的编写与细节处理现在我们已经有了N, e1, e2, c1, c2。接下来实现攻击。import math from Crypto.Util.number import long_to_bytes def egcd(a, b): “”“扩展欧几里得算法返回 (gcd, s, t)”“” if b 0: return (a, 1, 0) else: g, s1, t1 egcd(b, a % b) # 根据递归结果计算当前层的 s, t s t1 t s1 - (a // b) * t1 return (g, s, t) def common_modulus_attack(N, e1, e2, c1, c2): # 1. 检查e1和e2是否互质 if math.gcd(e1, e2) ! 1: print(“e1 和 e2 不互质共模攻击可能不适用或需要变种。“) return None # 2. 使用扩展欧几里得算法求系数 s 和 t g, s, t egcd(e1, e2) # g 应该是1因为我们已经检查过互质 print(f“贝祖等式系数: s{s}, t{t}“) # 3. 处理负数指数 # 如果 s 是负数我们需要计算 c1 的模逆元的 |s| 次方 # 如果 t 是负数同理处理 c2 if s 0: # 计算 c1 在模 N 下的逆元 # 注意这里需要确保 gcd(c1, N) 1在RSA中密文通常满足此条件 c1_inv pow(c1, -1, N) # Python 3.8 支持 pow(a, -1, N) 求逆元 part1 pow(c1_inv, -s, N) else: part1 pow(c1, s, N) if t 0: c2_inv pow(c2, -1, N) part2 pow(c2_inv, -t, N) else: part2 pow(c2, t, N) # 4. 计算明文 m (part1 * part2) % N m (part1 * part2) % N return m # 调用攻击函数 m common_modulus_attack(N, e1, e2, c1, c2) if m is not None: # 尝试将解密出的整数转换为字节即flag flag long_to_bytes(m) print(f“恢复的明文 (整数): {m}“) print(f“尝试转换为字节: {flag}“)脚本要点解析互质检查这是攻击成立的前提。虽然理论上不互质也有办法但绝大多数CTF题目都设计为互质。扩展欧几里得算法函数egcd是标准实现务必理解其递归过程。它返回的s和t可能一正一负。负数指数处理这是最容易出错的地方。pow(a, -1, N)是求模逆元非常简洁的写法Python 3.8。对于更低版本的Python需要使用gmpy2.invert(a, N)或自己实现扩展欧几里得来求逆元。最终计算m (c1^s * c2^t) mod N注意乘法后要取模。3.3 结果处理与Flag提取运行脚本后m是一个大整数。我们需要将其转换为可读的字符串。通常使用long_to_bytes。但CTF的flag可能带有特定格式如flag{...}、GXY{...}转换后直接打印即可。有时解密出的m可能还不是最终的flag因为它可能只是RSA加密前的“填充后消息”。在简单的CTF题中m往往直接就是明文的整数表示。如果输出是一串乱码可以尝试不同的编码方式如UTF-8, ASCII或者观察开头字节是否符合常见文件头如PNG PDF这可能提示flag被进一步编码或隐藏在了文件里。4. 拓展与防御从解题到现实安全思考解出这道题并不意味着共模攻击的知识点就结束了。恰恰相反这是一个起点让我们思考更深入的问题和真正的安全实践。4.1 攻击的变种与更复杂场景更多组密文如果同一明文被k组不同的(e_i, c_i)加密共享同一个N且所有e_i两两互质我们仍然可以使用扩展欧几里得算法推广形式或递归使用找到一组系数使得Σ(e_i * s_i) 1从而恢复明文。这要求我们对贝祖等式有更深的理解。明文线性相关有时加密的明文并非完全相同而是存在线性关系例如m2 a*m1 b (mod N)。结合共模攻击和其他代数手段也可能构成攻击。这需要更强的代数分析能力。与其它攻击结合如果N相同但e不互质且明文相同那么gcd(e1, e2) g 1。我们可以先计算c1^(e2/g)和c2^(e1/g)理论上它们都等于m^(e1*e2/g)然后通过计算这两个结果的g次剩余来尝试恢复m。这比标准共模攻击复杂得多。4.2 如何在现实中避免共模攻击这道CTF题直接映射了一个真实世界的安全漏洞RSA密钥生成过程中重复使用模数N。以下是绝对的安全准则为每个密钥对生成独立的 (p, q)这是铁律。无论是用于加密还是签名每个RSA密钥对应的一对质数(p, q)必须是独立、随机生成的大素数。绝对禁止将一个密钥对的N用于另一个密钥对。使用可靠的密码学库现代密码学库如OpenSSL, libsodium, 各类语言的标准密码模块在生成RSA密钥时都会确保p和q的随机性。不要自己手写质数生成和密钥派生逻辑。密钥管理在大型系统中妥善管理密钥的生命周期确保废弃的密钥被安全删除避免新旧密钥混淆。理解协议规范在一些复杂的协议中如某些早期的TLS配置或自定义协议如果设计不当可能会无意中诱导出“同一消息用不同公钥加密”的场景。设计协议时应确保即使模数意外重复也不会导致同一明文被多次加密。4.3 CTF中的常见“坑点”与解题技巧回到CTF赛场除了标准的共模攻击出题人可能会设置一些障碍来增加趣味性数据隐藏N, e, c可能不是直接给出的。它们可能隐藏在流量包pcap里、编码在图片的像素中LSB隐写、或者通过网页源代码的特定注释给出。需要具备基本的数据提取和编码识别能力Hex, Base64, Base58, 十进制等。非标准格式公钥可能不是PEM格式而是裸的(N, e)对以某种分隔符如逗号、换行存放在文本中。密文也可能是一串非常长的十进制数字。灵活使用Python的int()和字符串处理是关键。需要猜测明文格式解密出的m转成字节后可能还不是flag。它可能是一段提示需要进一步ROT13、Base64解码、或是一个压缩包的密码。养成多尝试几种解码方式的习惯。工具链准备本地准备好一个密码学解题环境非常有用。我常用的工具链包括Pythonpycryptodome/gmpy2用于核心算法实现和快速计算。SageMath对于更复杂的数论问题或需要在高阶代数环上运算时Sage是神器。RSACTFTool或RsaCtfTool优秀的开源工具集集成了数十种RSA攻击方法。在理解原理后可以用它们来快速验证思路或处理一些繁琐的计算。但比赛时不能完全依赖自己写脚本的能力更重要。在线分解网站如 factordb.com对于较小的N通常小于512位可以尝试直接分解。但共模攻击的精妙之处就在于无需分解。5. 从这道题延伸的密码学学习路径如果你通过这道题对RSA产生了兴趣那么可以沿着以下路径继续深入学习夯实数论基础欧几里得算法、扩展欧几里得算法、模运算、欧拉定理、费马小定理、中国剩余定理CRT。这些是理解几乎所有公钥密码学的基石。掌握RSA的所有经典攻击小公钥指数攻击e3, 17等小私钥指数攻击Wiener‘s Attack, Boneh-Durfee Attack因式分解攻击当p和q选择不当时p/q相近、p-1光滑、费马分解等侧信道攻击计时攻击、功耗分析——这更多是物理安全范畴。选择密文攻击CCA——理解PKCS#1 v1.5填充的缺陷。学习填充方案原始的“教科书式RSA”即直接对消息整数加密是不安全的。必须使用填充方案如OAEP最优非对称加密填充。理解为什么填充是必须的。探索椭圆曲线密码学ECC在理解了RSA的原理和脆弱性后可以学习更现代的ECC。它能在更短的密钥长度下提供同等级别的安全性但背后的数学椭圆曲线离散对数问题也更为复杂。最后我个人在多次CTF比赛和实际研究中的体会是密码学题目就像一个个精致的谜题。解题的快感不仅在于拿到flag的那一刻更在于一步步拆解、验证、最终洞察其设计精妙的过程。“Common Modulus Attack”这道题就是一个完美的入门砖它用最简洁的设定揭示了非对称加密中一个深刻而反直觉的漏洞。理解它你就拿到了开启RSA攻击世界大门的钥匙。下次再看到两个共享N的公钥时你一定会会心一笑。

相关新闻