保险箱与双钥匙:用生活场景透视RSA共模攻击
想象你有一个公共保险箱,这个箱子设计得相当特别——它允许不同的人用各自的钥匙上锁,但开锁时需要所有上锁的钥匙配合才能打开。有一天,Alice用她的钥匙A锁上了箱子,随后Bob又用钥匙B在已经锁住的箱子上加了一道锁。按照正常流程,要打开这个箱子必须同时具备A、B两把钥匙。但如果我们发现这两把钥匙之间存在某种数学关系,是否有可能绕过这个限制呢?这就是RSA共模攻击在现实世界中的生动映射。
1. 从保险箱到模数:建立直观认知
那个公共保险箱就是RSA加密系统中的模数n——一个由两个大素数相乘得到的巨大数字。在传统RSA中,每个用户拥有自己独特的公钥(e)和私钥(d),就像每个人都有自己专属的保险箱钥匙。但当多个用户共享同一个模数n时,就相当于大家都在使用同一个物理保险箱,只是用不同的方法上锁。
有趣的是:保险箱的物理结构(n)一旦确定就无法更改,就像RSA中的模数通常是固定的一样。而不同的上锁方式(e1,e2)则对应不同的公钥指数。
为什么共享模数会有风险?
因为保险箱的构造原理(素数分解)是所有锁具的共同基础。如果攻击者获取了用不同钥匙(e1,e2)加密的同一份文件(m)的两个版本(c1,c2),就能像侦探一样找出锁具设计中的漏洞:
- 观察到两把钥匙的转动角度(e1,e2)存在数学关联
- 发现可以通过组合转动(扩展欧几里得算法)找到开锁的"巧劲"
- 最终不需要任何一把原始钥匙就能打开保险箱
2. 锁匠的数学:欧几里得扩展算法的妙用
回到我们的保险箱比喻,假设Alice的钥匙需要顺时针旋转11圈(e1=11),Bob的钥匙需要逆时针旋转9圈(e2=9)。神奇的是,这两组旋转可以组合出一个"虚拟钥匙":
11圈顺时针 + 9圈逆时针 = 相当于2圈净顺时针旋转 再调整一下: (11×5) + (9×(-6)) = 1这意味着如果我们把Alice的锁旋转5次,同时把Bob的锁反向旋转6次,就能产生相当于旋转1圈的效果——正好打开保险箱!这就是扩展欧几里得算法在共模攻击中的实际应用。
用Python代码表示这个"锁匠技巧":
import gmpy2 # 假设已知e1=11, e2=9 r, s1, s2 = gmpy2.gcdext(11, 9) print(f"组合系数:{s1}和{s2}") # 输出5和-6关键点:s1和s2的正负决定了旋转方向,就像钥匙的顺时针/逆时针转动。当e1和e2互质时,这个方程一定有解。
3. 组装虚拟钥匙:从理论到实践的跨越
现在我们把生活比喻映射回真实的RSA参数。已知:
- 公共保险箱规格:n
- Alice的锁具参数:e1, c1
- Bob的锁具参数:e2, c2
通过扩展欧几里得算法找到的s1和s2,就是我们的"虚拟钥匙组合系数"。实际操作中:
- 对c1进行s1次方运算(相当于应用Alice的锁具s1次)
- 对c2进行s2次方运算(相当于应用Bob的锁具s2次)
- 将两个结果相乘(组合两种锁具操作)
- 最后对n取模(确认保险箱的最终状态)
数学表达式为:
m = (pow(c1,s1,n) * pow(c2,s2,n)) % n这就像是在说:"如果我们把Alice的锁具操作重复s1次,同时把Bob的锁具操作反向进行s2次,两者组合的效果就相当于直接打开了保险箱。"
4. 实战演练:破解BUUCTF的RSA3题目
让我们用实际的CTF题目来验证这个比喻。题目提供了:
- 公共模数n
- 两套公钥和密文:(e1,c1)和(e2,c2)
解题脚本就像是一套专业的锁匠工具:
from Crypto.Util.number import long_to_bytes from gmpy2 import gmpy2 # 题目数据 n = 2270807881588501146... # 省略具体数值 e1, c1 = 11187289, 2232203527566323704... e2, c2 = 9647291, 1870201004518701555... # 寻找虚拟钥匙组合 r, s1, s2 = gmpy2.gcdext(e1, e2) # 组装虚拟钥匙 m = (pow(c1, s1, n) * pow(c2, s2, n)) % n print(long_to_bytes(m)) # 输出:b'flag{49d91077a1abcb14f1a9d546c80be9ef}'操作要点:
gcdext函数找出s1和s2的组合系数pow(c1,s1,n)相当于应用Alice的锁具操作- 注意s2可能是负数,这时需要计算模逆元
5. 防御策略:如何设计更安全的保险箱
理解了共模攻击的原理后,我们自然想知道如何防范。就像保险箱设计师会从攻击中吸取教训一样:
绝对避免的做法:
- 多个用户共享同一个模数n(相当于使用同一个物理保险箱)
- 使用不互质的公钥指数(就像两把钥匙旋转圈数有公约数)
推荐的安全实践:
- 每个用户独立生成自己的n(每人有专属保险箱)
- 使用65537作为公钥指数(经过验证的安全选择)
- 对同一消息使用不同填充方案(相当于给锁具增加独特纹路)
| 安全措施 | 类比解释 | 技术实现 |
|---|---|---|
| 独立模数 | 每人独立保险箱 | 随机生成不同p,q |
| 固定公钥指数 | 标准化锁具设计 | e=65537 |
| 随机填充 | 锁具独特纹路 | OAEP填充方案 |
在实际开发中,直接使用成熟的RSA库(如Python的cryptography)比手动实现更安全,就像聘请专业保险箱设计师比自己DIY更可靠一样。