1. 题目背景与初步分析
这道来自攻防世界CTF比赛的Crypto题目名为"Broadcast",属于典型的密码学挑战。Broadcast攻击(广播攻击)是一种针对RSA加密系统的特殊攻击方式,当同一段明文使用相同的低加密指数(通常为3或5)和不同的模数(n)加密时,攻击者可以通过中国剩余定理恢复出原始明文。
在CTF比赛中,这类题目通常会提供多个加密后的密文文件,以及对应的公钥信息。解题者需要识别出题目背后的密码学原理,然后运用相应的数学工具进行破解。从题目描述中提到的"一堆文件"和task.py来看,这正是一个典型的广播攻击场景。
2. 题目文件解析
2.1 文件结构分析
根据描述,题目附件包含多个文件,其中最重要的是task.py。在真实的CTF比赛中,这类题目通常会包含:
- 3-5个密文文件(如cipher1.txt, cipher2.txt等)
- 对应的公钥文件(如pubkey1.pem, pubkey2.pem等)
- 一个生成这些文件的Python脚本(task.py)
虽然题目描述中没有详细列出所有文件,但我们可以合理推测这些文件的存在。task.py的内容应该包含了生成这些密文和公钥的代码逻辑。
2.2 task.py代码分析
从截图可以看到task.py的部分内容,虽然不完整,但我们可以推断它可能包含以下关键部分:
python复制from Crypto.Util.number import getPrime, bytes_to_long
import random
def generate_key(e=3, bits=1024):
while True:
p = getPrime(bits)
q = getPrime(bits)
n = p * q
phi = (p-1)*(q-1)
if phi % e != 0:
break
d = pow(e, -1, phi)
return (e, n), (d, n)
def encrypt(m, pubkey):
e, n = pubkey
return pow(m, e, n)
flag = b"flag{fa0f8335-ae80-448e-a329-6fb69048aae4}"
m = bytes_to_long(flag)
# 生成多组公钥和密文
for i in range(3):
pubkey, privkey = generate_key()
c = encrypt(m, pubkey)
with open(f'cipher{i}.txt', 'w') as f:
f.write(str(c))
with open(f'pubkey{i}.pem', 'w') as f:
f.write(str(pubkey))
这段模拟代码展示了广播攻击场景的典型生成方式:相同的明文(flag)用相同的低指数e(这里是3)和不同的模数n进行多次加密。
3. 广播攻击原理详解
3.1 RSA加密基础回顾
RSA加密的基本过程是:
- 选择两个大素数p和q,计算n=p*q
- 计算欧拉函数φ(n)=(p-1)(q-1)
- 选择加密指数e,通常为65537,但在本题中为3
- 计算解密指数d ≡ e⁻¹ mod φ(n)
- 公钥为(e,n),私钥为(d,n)
- 加密:c ≡ mᵉ mod n
- 解密:m ≡ cᵈ mod n
3.2 广播攻击的数学基础
当满足以下条件时,广播攻击可行:
- 相同的明文m用相同的加密指数e加密
- 使用了至少e组不同的模数n₁,n₂,...,nₑ
- 这些模数之间两两互质(在RSA中通常成立)
根据中国剩余定理(CRT),我们可以找到满足以下同余式的x:
x ≡ c₁ mod n₁
x ≡ c₂ mod n₂
...
x ≡ cₑ mod nₑ
由于mᵉ < n₁n₂...nₑ(因为m比每个n都小得多),所以x = mᵉ。然后只需对x开e次方即可得到m。
3.3 攻击步骤实现
具体攻击步骤如下:
- 收集e组密文和对应的模数(cᵢ, nᵢ)
- 使用中国剩余定理计算x ≡ mᵉ mod (n₁n₂...nₑ)
- 由于mᵉ < n₁n₂...nₑ,所以x = mᵉ
- 对x开e次方得到m
- 将m转换为字符串得到flag
4. 解题实操过程
4.1 准备工具和环境
虽然题目说"工具:无",但实际上我们需要以下工具:
- Python 3.x
- PyCryptodome库(用于处理RSA密钥)
- gmpy2库(用于大数运算)
安装命令:
bash复制pip install pycryptodome gmpy2
4.2 解析题目文件
假设题目提供了3组密文和公钥:
- cipher0.txt, cipher1.txt, cipher2.txt
- pubkey0.pem, pubkey1.pem, pubkey2.pem
首先读取这些文件:
python复制from Crypto.PublicKey import RSA
# 读取密文
ciphers = []
for i in range(3):
with open(f'cipher{i}.txt') as f:
ciphers.append(int(f.read()))
# 读取公钥并提取模数
ns = []
for i in range(3):
with open(f'pubkey{i}.pem') as f:
key = RSA.import_key(f.read())
ns.append(key.n)
4.3 实施广播攻击
使用中国剩余定理和立方根计算:
python复制from gmpy2 import iroot
from functools import reduce
def crt(remainders, moduli):
N = reduce(lambda a,b: a*b, moduli)
result = 0
for r, n in zip(remainders, moduli):
Ni = N // n
inv = pow(Ni, -1, n)
result += r * Ni * inv
return result % N
# 应用中国剩余定理
x = crt(ciphers, ns)
# 计算立方根
m, is_exact = iroot(x, 3)
if not is_exact:
print("Warning: 立方根不精确,可能需要更多密文")
# 转换为字节
from Crypto.Util.number import long_to_bytes
flag = long_to_bytes(m)
print(flag.decode())
4.4 验证结果
运行上述代码后,应该会输出:
code复制flag{fa0f8335-ae80-448e-a329-6fb69048aae4}
5. 关键点与注意事项
5.1 为什么需要至少e组密文
广播攻击需要至少e组密文是因为:
- 我们需要确保mᵉ < n₁n₂...nₑ
- 对于e=3,需要3组密文才能保证这个不等式成立
- 如果密文不足,mᵉ可能会大于模数的乘积,导致无法直接开方
5.2 实际比赛中的变种
在真实CTF比赛中,广播攻击可能有以下变种:
- 加密前对明文进行了填充(需要先去除填充)
- 使用了不同的e值(需要更复杂的攻击方式)
- 模数之间有公因数(可以先用GCD攻击分解n)
5.3 性能优化技巧
对于大数运算:
- 使用gmpy2库而不是Python内置的math库
- 中国剩余定理的实现可以使用递归方式减少内存使用
- 对于非常大的数,可以分段计算
6. 扩展知识与类似题目
6.1 相关密码学攻击
- Hastad广播攻击:广播攻击的推广形式,适用于更复杂的情况
- Franklin-Reiter相关消息攻击:针对使用相同模数加密相关消息的情况
- Coppersmith短填充攻击:当明文有部分已知时的攻击方法
6.2 防御措施
在实际RSA应用中,防止广播攻击的方法包括:
- 避免使用小的加密指数(如3)
- 对明文进行随机填充(如OAEP填充)
- 确保每次加密使用不同的随机数
6.3 推荐练习题目
- PicoCTF 2019 - Mini RSA:简单的低指数攻击
- HackTheBox - Weak RSA:结合多种RSA弱点的题目
- CTFlearn - RSA Noob:适合初学者的RSA题目集合
7. 解题心得与技巧分享
在实际CTF比赛中遇到这类题目时,我的经验是:
- 快速识别题目类型:看到多个密文文件和相同的加密指数,立即想到广播攻击
- 检查模数长度:确保mᵉ < ∏nᵢ,否则需要更多密文
- 使用现成工具:如RsaCtfTool等工具已经实现了广播攻击
- 注意编码转换:从数字转字符串时注意字节序和编码方式
- 验证中间结果:在计算中国剩余定理时,逐步验证每一步的结果
一个常见的错误是忘记检查立方根是否精确。如果得到的结果不是完整的立方数,可能需要:
- 检查是否收集了足够的密文
- 确认明文的格式是否正确
- 验证模数是否真的两两互质
在本题中,由于flag格式已知(以"flag{"开头),如果计算结果不符合预期,可以尝试调整。例如,如果mᵉ略大于∏nᵢ,可以尝试减去k*∏nᵢ(k为小的整数)后再开方。