1. 题目背景与核心挑战解析
这道来自HDCTF2019的bbbbbbrsa题目,是典型的RSA弱密钥攻击类CTF赛题。题目名称中的"bbbbbb"暗示了密钥生成过程中存在某种规律性缺陷,这种命名方式在CTF比赛中常见于提示攻击方向。根据历年赛事经验,这类题目通常考察选手对RSA数学原理的理解以及利用工具进行密码分析的能力。
题目文件通常包含以下要素:
- 一个被多次加密的flag文件
- 可能残缺的RSA公钥参数(n, e)
- 加密脚本或加密过程的文字描述
- 有时会附带hint文件
从技术角度看,此类题目的核心突破口往往在于:
- 模数n的分解(小素数、共用素数、特殊生成方式)
- 加密指数e的特殊取值(极大值、极小值、与φ(n)不互质)
- 加密过程的实现漏洞(如多次加密、固定IV等)
- 填充方案的缺陷(无填充、固定填充等)
2. 初始分析与文件处理
2.1 题目文件获取与初步检查
首先从BUUOJ平台下载题目附件,典型的文件结构可能是:
code复制bbbbbbrsa/
├── encrypted.flag
├── pubkey.pem
└── encrypt.py
使用file命令检查文件类型:
bash复制file encrypted.flag # 通常显示为data
file pubkey.pem # 应显示为PEM证书
2.2 公钥信息提取
使用openssl提取公钥参数:
bash复制openssl rsa -pubin -in pubkey.pem -text -noout
典型输出示例:
code复制Public-Key: (256 bit)
Modulus:
00:a3:57:... [完整十六进制]
Exponent: 65537 (0x10001)
关键观察点:
- 模数n的位数(256bit说明可暴力分解)
- 指数e的取值(65537是常见值)
- 是否存在异常的密钥结构
2.3 Python加密脚本分析
如果提供encrypt.py,其核心内容可能如下:
python复制from Crypto.Util.number import *
import os
flag = open("flag").read()
p = getPrime(128)
q = getPrime(128)
n = p * q
e = 65537
m = bytes_to_long(flag + os.urandom(8))
c = pow(m, e, n)
open("encrypted.flag","wb").write(long_to_bytes(c))
open("pubkey.pem","wb").write(
bytes(open("pubkey_template.pem").read()
.replace("$MODULUS$", hex(n)[2:])
.replace("$EXPONENT$", hex(e)[2:]), "utf-8"))
需要注意的细节:
- 明文拼接了8字节随机数(影响解密结果)
- 模数n由两个128位素数相乘(256bit)
- 使用标准PKCS#1 PEM格式存储公钥
3. RSA参数分析与攻击面定位
3.1 模数n的分解尝试
对于256bit的n,可以直接使用在线分解工具或本地工具:
python复制from sympy import factorint
n = 0xa357... # 替换为实际模数
factors = factorint(n)
print(factors) # 输出质因数分解结果
如果分解成功,将得到:
code复制{p: 123..., q: 456...} # 两个128位素数
3.2 特殊指数情况检查
检查以下特殊情况:
- 极小e(如e=3)可能导致低加密指数攻击
- e与φ(n)不互质导致无法解密
- 极大e可能对应d较小,适用Wiener攻击
计算φ(n) = (p-1)*(q-1),验证gcd(e, φ(n)) == 1
3.3 题目名称提示分析
"bbbbbbrsa"中的重复b可能暗示:
- 模数n的二进制形式包含连续相同位
- 素数p或q的生成有规律(如固定前缀)
- 使用了类似bbbbb作为密码的弱密钥
4. 密钥恢复与解密过程
4.1 计算私钥参数
成功分解n后,计算:
python复制from math import gcd
from Crypto.Util.number import inverse
p = 123... # 实际p值
q = 456... # 实际q值
e = 65537
phi = (p-1)*(q-1)
d = inverse(e, phi)
4.2 密文解密处理
读取密文并转换:
python复制with open("encrypted.flag", "rb") as f:
c = bytes_to_long(f.read())
m = pow(c, d, n)
flag = long_to_bytes(m)
4.3 随机数处理
由于加密时附加了8字节随机数,解密结果末尾会有垃圾字符:
code复制b'flag{real_flag_value}\x12\xa3...\xfe'
正确提取flag:
python复制flag = flag.split(b'flag{')[-1].split(b'}')[0]
print(b'flag{' + flag + b'}')
5. 典型问题与调试技巧
5.1 常见错误排查
-
bytes_to_long/long_to_bytes使用错误:
- 确保字节序一致(通常大端序)
- 处理文件时用rb模式读取
-
模数分解失败:
- 尝试yafu等更强大的分解工具
- 检查是否为特殊形式的合数(如费马数)
-
解密结果乱码:
- 检查是否遗漏填充处理
- 验证p和q的正确性
5.2 性能优化技巧
对于大数运算:
python复制# 使用gmpy2加速
import gmpy2
d = int(gmpy2.invert(e, phi))
m = int(pow(c, d, n))
5.3 自动化脚本示例
完整解密脚本框架:
python复制from Crypto.Util.number import *
import gmpy2
# 1. 提取公钥参数
n = 0xa357... # 实际模数
e = 65537
# 2. 分解n (示例值)
p = 123...
q = 456...
# 3. 计算私钥
phi = (p-1)*(q-1)
d = int(gmpy2.invert(e, phi))
# 4. 解密
with open("encrypted.flag","rb") as f:
c = bytes_to_long(f.read())
m = pow(c, d, n)
flag = long_to_bytes(m)
# 5. 处理随机填充
print(flag[:flag.find(b'}')+1])
6. 扩展知识与类似题型
6.1 相关CTF题型
-
共用模数攻击:
- 多个密文使用相同n不同e
- 适用中国剩余定理恢复明文
-
小d攻击:
- Wiener攻击(d < n^0.25)
- Boneh-Durfee攻击(d < n^0.292)
-
选择密文攻击:
- 通过oracle获取部分信息
6.2 实用工具推荐
-
RsaCtfTool:
bash复制
python RsaCtfTool.py --publickey pubkey.pem --private -
yafu(因数分解):
bash复制yafu "factor(0xa357...)" -
sage数学库:
python复制# Wiener攻击实现示例 def wiener_attack(e, n): # ...实现代码... return d
6.3 进阶学习资源
-
数学基础:
- 欧拉定理与费马小定理
- 中国剩余定理
- 连分数展开
-
经典论文:
- "Twenty Years of Attacks on the RSA Cryptosystem"
- "Low-Exponent RSA with Related Messages"
-
实战平台:
- Cryptohack.org
- OverTheWire的RSA挑战
7. 解题总结与经验分享
在本次bbbbbbrsa题解过程中,有几个关键经验值得记录:
-
自动化参数提取:
写一个通用脚本自动从pem文件提取n和e:python复制from Crypto.PublicKey import RSA with open("pubkey.pem") as f: key = RSA.importKey(f.read()) n, e = key.n, key.e -
边界情况处理:
- 当flag被截断时,尝试多种编码方式
- 对解密结果进行正则匹配:re.search(b"flag{.*?}", m)
-
调试技巧:
- 在关键步骤打印中间值(如p,q,phi,d)
- 对异常结果进行hexdump分析
-
比赛策略:
- 先尝试最简单攻击路径(小n分解)
- 保留所有中间计算结果
- 注意题目名称和描述的隐藏提示
最后需要提醒的是,实际比赛中可能会遇到各种变种:
- 多层RSA加密
- 自定义的padding方案
- 结合其他密码算法的混合系统
- 需要自行构造明文的挑战
掌握基础RSA攻击方法后,建议通过大量练习积累经验,培养对脆弱参数的敏感度。对于想深入密码学方向的选手,还需要系统学习数论知识和现代密码分析技术。
