文章总结: 本文详细解析了一道RSA密码学CTF题目,通过分析泄露的部分私钥片段(包含CRT参数dp、dq、qinv),利用数学关系dp×e-1=k×(p-1)成功恢复出质数p和q,进而计算出完整私钥d并解密获取flag。文章涵盖了ASN.1DER编码解析、扩展欧几里得算法、素性测试等关键技术,并提供了完整的Python解题脚本,展示了从部分密钥泄露到完整密钥恢复的完整攻击链,强调了私钥保护的重要性。 综合评分: 92 文章分类: CTF,密码学,漏洞分析,安全开发,渗透测试
第一步:观察与分析题目
1.1 查看加密文件
首先,让我们看看加密文件的基本信息:
$ file flag.enc
flag.enc: data
$ ls -lh flag.enc
-rw-r--r-- 1 user user 128 Mar 11 2016 flag.enc
这是一个128字节的二进制文件,大小正好是1024位(128字节 × 8位/字节 = 1024位),这暗示使用的是1024位RSA密钥。
1.2 查看mask.png图片
打开mask.png,我们看到一个终端界面,显示了cat key.pem命令的输出:
-----BEGIN RSA PRIVATE KEY-----
[前面部分被遮挡/截断]
Os9mhOQRdqW2cwVrnNI72DLcAXpXUJ1HGwJBANWiJcDUGxZpnERxVw7s0913WXNt
V4GqdxCzG0pG5EHThtoTRbyX0aqRP4U/hQ9tRoSoDmBn+3HPITsnbCy67VkCQBM4
xZPTtUKM6Xi+16VTUnFVs9E4rqwIQCDAxn9UuVMBXlX2Cl0xOGUF4C5hItrX2woF
7LVS5EizR63CyRcPovMCQQDVyNbcWD7N88MhZjujKuSrHJot7WcCaRmTGEIJ6TkU
8NWt9BVjR4jVkZ2EqNd0KZWdQPukeynPcLlDEkIXyaQx
-----END RSA PRIVATE KEY-----
关键观察:
- 这是PEM格式的RSA私钥
- 私钥的前面部分被遮挡,但后面部分可见
- 可见部分约300个字符的Base64编码数据
1.3 初步思路
RSA私钥包含多个参数,如果我们能从可见的部分提取出关键参数,或许可以利用RSA参数之间的数学关系来恢复完整的私钥。
第二步:理解RSA私钥结构
在尝试提取参数之前,我们需要理解RSA私钥的存储格式。
2.1 RSA基础知识
RSA加密算法涉及以下关键参数:
公钥参数:
n:模数,n = p × qe:公钥指数,通常是65537
私钥参数:
d:私钥指数,满足 d × e ≡ 1 (mod φ(n))p:第一个质数q:第二个质数
CRT优化参数(中国剩余定理):
dp:d mod (p-1),也称为exponent1dq:d mod (q-1),也称为exponent2qinv:q⁻¹ mod p,也称为coefficient
2.2 PKCS#1 私钥格式
根据PKCS#1标准,RSA私钥的ASN.1结构定义如下:
RSAPrivateKey ::= SEQUENCE {
version INTEGER, -- 版本号,通常是0
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- dp = d mod (p-1)
exponent2 INTEGER, -- dq = d mod (q-1)
coefficient INTEGER, -- qinv = q^-1 mod p
}
这些参数按照上述顺序存储在私钥中。注意:后三个参数(exponent1、exponent2、coefficient)是为了加速RSA运算而存储的冗余信息。
2.3 ASN.1 DER编码
ASN.1使用DER(Distinguished Encoding Rules)编码规则将数据结构编码为二进制:
TLV结构:
-
T (Type):标识数据类型,1个字节
-
0x02= INTEGER(整数) -
0x30= SEQUENCE(序列) -
L (Length):数据长度
-
如果长度 < 128:直接用1个字节表示
-
如果长度 ≥ 128:第一字节最高位为1,剩余7位表示后续长度字节数
-
V (Value):实际数据
例如:02 41 00 D5 A2 25 ... 表示:
02:INTEGER类型41:长度为65字节(十进制)00 D5 A2 25 ...:65字节的整数值
第三步:提取私钥参数
3.1 Base64解码
首先将可见的Base64字符串解码为二进制数据:
import base64
base64_data = "Os9mhOQRdqW2cwVrnNI72DLcAXpXUJ1HGwJBANWiJcDUGxZpnERxVw7s0913WXNtV4GqdxCzG0pG5EHThtoTRbyX0aqRP4U/hQ9tRoSoDmBn+3HPITsnbCy67VkCQBM4xZPTtUKM6Xi+16VTUnFVs9E4rqwIQCDAxn9UuVMBXlX2Cl0xOGUF4C5hItrX2woF7LVS5EizR63CyRcPovMCQQDVyNbcWD7N88MhZjujKuSrHJot7WcCaRmTGEIJ6TkU8NWt9BVjR4jVkZ2EqNd0KZWdQPukeynPcLlDEkIXyaQx"
binary_data = base64.b64decode(base64_data)
print(f"解码后字节数: {len(binary_data)}") # 输出: 225
解码后得到225字节的二进制数据。
3.2 解析DER结构
查看二进制数据的十六进制表示:
3acf6684e41176a5b673056b9cd23bd832dc017a57509d471b024100d5a225c0
d41b16699c4471570eecd3dd7759736d5781aa7710b31b4a46e441d386da1345
bc97d1aa913f853f850f6d4684a80e6067fb71cf213b276c2cbaed5902401338
c593d3b5428ce978bed7a553527155b3d138aeac084020c0c67f54b953015e55
f60a5d31386505e02e6122dad7db0a05ecb552e448b347adc2c9170fa2f30241
00d5c8d6dc583ecdf3c321663ba32ae4ab1c9a2ded6702691993184209e93914
f0d5adf415634788d5919d84a8d77429959d40fba47b29cf70b943124217c9a4
31
现在我们需要找到所有的INTEGER类型标记(0x02)并提取其值:
def parse_der_integers(data):
"""解析DER编码中的所有INTEGER"""
integers = []
i = 0
while i < len(data):
if data[i] == 0x02: # INTEGER类型
length = data[i + 1]
if length <= 127: # 短格式长度
value_bytes = data[i + 2 : i + 2 + length]
value = int.from_bytes(value_bytes, byteorder='big')
integers.append({
'offset': i,
'length': length,
'value': value
})
i += 2 + length
else:
i += 1
else:
i += 1
return integers
integers = parse_der_integers(binary_data)
print(f"找到 {len(integers)} 个INTEGER")
3.3 提取的参数
运行解析代码后,我们找到了3个大整数:
第一个INTEGER: 偏移25,长度65字节
dp = 11188888442779478492506783674852186314949555636014740182307607993518479864690065244102864238986781155531033697982611187514703037389481147794554444962262361
第二个INTEGER: 偏移92,长度64字节
dq = 1006725509429627901220283238134032802363853505667837273574181077068133214344166038422298631614477333564791953596600001816371928482096290600710984197710579
第三个INTEGER: 偏移158,长度65字节
qinv = 11196804284042107547423407831525890933636414684075355664222816007929037065463409676450144484947842399975707117057331864113464711778199061912128258484839473
3.4 参数识别
根据RSA私钥的结构顺序,由于前面部分被截断,我们看到的应该是私钥末尾的三个参数。这三个参数很可能是:
- exponent1 (dp) = d mod (p-1)
- exponent2 (dq) = d mod (q-1)
- coefficient (qinv) = q⁻¹ mod p
这是个好消息!虽然我们没有直接得到p、q、d,但这三个CRT参数之间存在数学关系,理论上可以恢复出完整的私钥。
第四步:数学原理分析
现在的关键问题是:如何从dp、dq、qinv恢复出质数p和q?
4.1 寻找数学突破口
让我们回顾一下这些参数的定义:
dp = d mod (p-1)dq = d mod (q-1)qinv = q⁻¹ mod p
同时,RSA的基本性质告诉我们:
d × e ≡ 1 (mod φ(n))φ(n) = (p-1) × (q-1)
4.2 推导关键等式
由于 d × e ≡ 1 (mod φ(n)),而 (p-1) 是 φ(n) 的因子,我们有:
d × e ≡ 1 (mod (p-1))
因为 dp = d mod (p-1),所以:
dp × e ≡ d × e ≡ 1 (mod (p-1))
这意味着:
dp × e = 1 + k × (p-1) (对于某个整数k)
移项得到:
dp × e - 1 = k × (p-1)
关键洞察:(p-1) 必定是 (dp × e - 1) 的因子!
4.3 恢复策略
基于上述推导,我们可以设计如下算法:
- 计算
d1p = dp × e - 1 - 遍历可能的k值(从3到e)
- 对每个k,计算
p = (d1p / k) + 1 - 检验p是否为质数
- 如果p是质数,用相同方法恢复q
- 验证
qinv × q ≡ 1 (mod p)是否成立
4.4 为什么这个方法有效?
理论基础:
- 质数p的分布相对稀疏,根据质数定理,1024位RSA中的质数p约为512位
- 虽然k的取值范围很大(最大65537),但满足条件的k值非常少
- 一旦找到质数p,可以通过qinv关系验证其正确性
计算复杂度:
- 遍历k值:O(e) ≈ O(65537)
- 素性测试:O(log³ p)(Miller-Rabin算法)
- 总体上是可行的
第五步:实现参数恢复算法
5.1 完整实现
from Crypto.Util.number import isPrime
def recover_primes_from_crt(dp, dq, qinv, e=65537):
"""
从CRT参数恢复RSA质数p和q
参数:
dp: d mod (p-1)
dq: d mod (q-1)
qinv: q^-1 mod p
e: 公钥指数,默认65537
返回:
(p, q) 或 (None, None)
"""
print("开始恢复质数p...")
d1p = dp * e - 1
for k in range(3, e):
# 检查k是否是d1p的因子
if d1p % k == 0:
hp = d1p // k
p = hp + 1
# 验证p是否为质数
if isPrime(p):
print(f"找到候选质数p (k={k})")
# 同样方法恢复q
d1q = dq * e - 1
for m in range(3, e):
if d1q % m == 0:
hq = d1q // m
q = hq + 1
if isPrime(q):
# 验证qinv关系
if (qinv * q) % p == 1:
print(f"验证成功! 找到正确的p和q")
return p, q
elif (qinv * p) % q == 1:
print(f"验证成功! 找到正确的p和q")
return p, q
return None, None
5.2 执行恢复
# 使用提取的参数
dp = 11188888442779478492506783674852186314949555636014740182307607993518479864690065244102864238986781155531033697982611187514703037389481147794554444962262361
dq = 1006725509429627901220283238134032802363853505667837273574181077068133214344166038422298631614477333564791953596600001816371928482096290600710984197710579
qinv = 11196804284042107547423407831525890933636414684075355664222816007929037065463409676450144484947842399975707117057331864113464711778199061912128258484839473
e = 65537
# 恢复质数
p, q = recover_primes_from_crt(dp, dq, qinv, e)
5.3 恢复结果
程序成功找到:
k = 56917 时:
p = 12883429939639100479003058518523248493821688207697138417834631218638027564562306620214863988447681300666538212918572472128732943784711527013224777474072569
m = 5277 时:
q = 12502893634923161599824465146407069882228513776947707295476805997311776855879024002289593598657949783937041929668443115224477369136089557911464046118127387
验证: (qinv × q) mod p = 1 ✓
验证通过!我们成功恢复了RSA的两个质数。
5.4 结果验证
让我们验证恢复的参数是否正确:
# 验证1: 检查p和q是否为质数
print(f"p是质数: {isPrime(p)}") # True
print(f"q是质数: {isPrime(q)}") # True
# 验证2: 检查qinv关系
print(f"qinv验证: {(qinv * q) % p == 1}") # True
# 验证3: 重新计算dp和dq(需要先计算d)
n = p * q
phi = (p - 1) * (q - 1)
所有验证都通过,说明我们的恢复是正确的。
第六步:计算完整私钥
现在我们有了p、q和e,可以计算完整的RSA私钥。
6.1 计算私钥指数d
RSA私钥d满足:d × e ≡ 1 (mod φ(n))
这意味着d是e在模φ(n)下的乘法逆元。我们使用扩展欧几里得算法(Extended Euclidean Algorithm)来计算。
扩展欧几里得算法原理:
对于整数a和b,EGCD算法不仅能计算gcd(a,b),还能找到系数u和v使得:
a × u + b × v = gcd(a, b)
当 gcd(e, φ(n)) = 1 时(这是RSA的必要条件),我们有:
e × u + φ(n) × v = 1
两边对φ(n)取模:
e × u ≡ 1 (mod φ(n))
因此u就是我们要找的d。
6.2 实现EGCD算法
def extended_gcd(a, b):
"""
扩展欧几里得算法
返回u,使得 a*u ≡ 1 (mod b)
"""
u, u1 = 1, 0
v, v1 = 0, 1
while b:
q = a // b
u, u1 = u1, u - q * u1
v, v1 = v1, v - q * v1
a, b = b, a - q * b
return u
# 计算n和φ(n)
n = p * q
phi = (p - 1) * (q - 1)
print(f"n = {n}")
print(f"φ(n) = {phi}")
# 计算d
d = extended_gcd(e, phi)
if d < 0:
d += phi
print(f"d = {d}")
6.3 计算结果
n = 161080154188292201430717335450301702574211890587423028785946588452513709903864566907797711402814280216429284407010865117658741411399738837015270166197792615276511302372234182990420185803542388458087342116253675425489502589540709488892694405415013333511961708962693793627275736479090319881934245022826824347203
φ(n) = 161080154188292201430717335450301702574211890587423028785946588452513709903864566907797711402814280216429284407010865117658741411399738837015270166197792589890187727809972104162896520873224012407885357471407962114052286639736289047562071900957426227880877105382551206611688383268777399080849320334003232147248
d = 12441639692099655517376308833932392670257420848582256919212988552216677594845086557017745931627109670194928630671056032860651983223301005431608062335676428430110171020554477490159485308455680772826276447201841772149055876380443034602731403064627486237285806604612267999273183028007861118868108999965277036321
6.4 验证私钥
验证私钥d是否正确:
# 验证: d × e ≡ 1 (mod φ(n))
verify = (d * e) % phi
print(f"验证结果: {verify}") # 应该等于1
输出:1 ✓
完美!我们现在拥有了完整的RSA私钥参数:p、q、n、e、d。
第七步:RSA解密
7.1 读取加密文件
from Crypto.Util.number import bytes_to_long, long_to_bytes
# 读取密文
with open("flag.enc", "rb") as f:
ciphertext_bytes = f.read()
print(f"密文长度: {len(ciphertext_bytes)} 字节")
print(f"密文(hex): {ciphertext_bytes.hex()}")
# 转换为整数
ciphertext = bytes_to_long(ciphertext_bytes)
print(f"密文(int): {ciphertext}")
输出:
密文长度: 128 字节
密文(hex): 20aede3c132c55736c4b564de173f9b6d1f12a9878f567baf3563a711ced56f3...
密文(int): 22950838243355051507735476344130216859576434035671340145571506267533...
7.2 执行RSA解密
RSA解密公式:
明文 = 密文^d mod n
# RSA解密
plaintext = pow(ciphertext, d, n)
plaintext_bytes = long_to_bytes(plaintext)
print(f"明文(hex): {plaintext_bytes.hex()}")
print(f"明文(bytes): {plaintext_bytes}")
输出:
明文(hex): 02f6d04bd49ea1f49066c07ab9a4645e35049e2adaebecf5a6685c88d1198ddf13c89f9e33ea7d9076...
明文(bytes): b'\x02\xf6\xd0K\xd4\x9e\xa1\xf4\x90f\xc0z\xb9\xa4d^5\x04\x9e*...\x000ctf{Keep_ca1m_and_s01ve_the_RSA_Eeeequati0n!!!}\n'
7.3 PKCS#1 v1.5 填充分析
仔细观察解密结果,我们发现:
- 开头是
\x02 - 中间有很多随机字节
- 后面有
\x00 - 最后是可读的文本
这是PKCS#1 v1.5填充格式!
PKCS#1 v1.5 填充格式:
0x00 || 0x02 || PS || 0x00 || M
其中:
- 第1字节:
0x00(通常在转换为整数时会丢失) - 第2字节:
0x02(表示加密操作,而不是签名) - PS:填充字符串,由至少8个随机非零字节组成
0x00:分隔符- M:实际的消息
7.4 提取实际消息
# 寻找分隔符0x00(从第3个字节开始)
separator_index = plaintext_bytes.find(b'\x00', 2)
if separator_index != -1:
# 提取分隔符之后的内容
actual_message = plaintext_bytes[separator_index + 1:]
flag = actual_message.decode('utf-8').strip()
print(f"找到分隔符位置: {separator_index}")
print(f"实际消息: {actual_message}")
print(f"Flag: {flag}")
else:
print("未找到PKCS#1填充分隔符")
输出:
找到分隔符位置: 77
实际消息: b'0ctf{Keep_ca1m_and_s01ve_the_RSA_Eeeequati0n!!!}\n'
Flag: 0ctf{Keep_ca1m_and_s01ve_the_RSA_Eeeequati0n!!!}
成功获取flag!
第八步:技术总结与深度分析
8.1 完整攻击链
让我们回顾整个攻击过程:
部分私钥泄露 (Base64片段)
↓
ASN.1 DER解析
↓
提取CRT参数 (dp, dq, qinv)
↓
数学关系推导 (dp×e-1 = k×(p-1))
↓
恢复质数 (p, q)
↓
计算私钥指数 (d)
↓
RSA解密
↓
去除PKCS#1填充
↓
获取flag
8.2 关键技术点深度解析
1. ASN.1 DER编码
ASN.1是密码学中广泛使用的数据描述语言。DER编码保证了相同数据结构只有唯一的编码结果,这对于数字签名等应用至关重要。
理解DER编码的重要性:
- 能够手工解析密码学文件格式
- 理解密钥的实际存储结构
- 为密码分析提供基础
2. CRT参数的安全隐患
RSA实现中使用CRT参数来加速解密运算。解密速度可以提升约4倍:
标准解密: m = c^d mod n
CRT解密:
m1 = c^dp mod p
m2 = c^dq mod q
h = qinv × (m1 - m2) mod p
m = m2 + h × q
然而,这些冗余参数带来了安全风险:
- 任何一个CRT参数的泄露都可能导致完整密钥恢复
- 本题展示了即使只有dp、dq、qinv,也能恢复p和q
3. 数学关系的威力
本题的核心在于利用了以下数学关系:
dp × e - 1 = k × (p-1)
这个关系来自于:
- RSA的基本性质:d × e ≡ 1 (mod φ(n))
- CRT参数的定义:dp = d mod (p-1)
- 模运算的性质:如果 a ≡ b (mod m),则 a×c ≡ b×c (mod m)
这种数学关系在密码分析中经常出现,理解这些关系是密码分析的基础。
4. 素性测试
在恢复算法中,我们需要快速判断一个大数是否为质数。常用算法:
- Miller-Rabin算法:概率性算法,速度快
- AKS算法:确定性算法,但速度较慢
对于密码学应用,Miller-Rabin算法已经足够,误判率可以降到2^-100以下。
5. 扩展欧几里得算法
EGCD是数论中的基础算法,应用广泛:
- 计算模逆元(本题使用)
- 求解线性丢番图方程
- 实现中国剩余定理
算法时间复杂度为O(log n),非常高效。
8.3 攻击的前提条件
本题攻击成功依赖于以下条件:
- 部分私钥泄露:泄露了CRT参数
- 使用标准参数:公钥指数e = 65537
- 参数完整性:泄露的参数是完整的,没有损坏
- 数学关系有效:RSA的数学结构允许从部分参数恢复全部
8.4 防御措施
从这道题我们可以学到以下安全教训:
1. 私钥保护
- 私钥的任何部分都不应该泄露
- 即使是”辅助参数”也可能导致完整密钥恢复
- 使用硬件安全模块(HSM)保护私钥
2. 密钥存储
- 私钥应该加密存储
- 使用强密码和密钥派生函数
- 定期轮换密钥
3. 访问控制
- 限制对私钥文件的访问
- 使用文件权限和SELinux等机制
- 审计私钥访问日志
4. 侧信道防护
- 防止时序攻击
- 防止功耗分析攻击
- 使用常量时间实现
5. 参数选择
- 使用足够长的密钥(推荐2048位或以上)
- 考虑使用更现代的密码算法(如ECC)
- 遵循密码学最佳实践
第九步:实战技巧与经验总结
9.1 CTF密码题解题思路
1. 信息收集
- 查看所有给定文件
- 分析文件类型和大小
- 寻找可能的提示信息
2. 识别密码体制
- 根据文件格式判断加密算法
- RSA、AES、ECC等各有特征
- 密文长度常常是关键线索
3. 寻找弱点
- 参数泄露
- 算法实现错误
- 密钥生成缺陷
- 填充oracle
4. 数学分析
- 参数之间的数学关系
- 是否存在小指数攻击
- 是否存在共模攻击
- 是否可以分解模数
5. 工具使用
- SageMath:强大的数学计算工具
- RsaCtfTool:自动化RSA攻击工具
- openssl:密码学工具集
- Python + Crypto库:脚本开发
9.2 Python密码学实践
本题涉及的Python密码学技术:
1. 数值转换
from Crypto.Util.number import bytes_to_long, long_to_bytes
# 字节转整数
num = bytes_to_long(b"Hello")
# 整数转字节
data = long_to_bytes(num)
2. 素性测试
from Crypto.Util.number import isPrime
# Miller-Rabin素性测试
is_prime = isPrime(p)
3. 模幂运算
# Python内置pow函数支持模幂运算
result = pow(base, exponent, modulus)
4. Base64操作
import base64
# 编码
encoded = base64.b64encode(data)
# 解码
decoded = base64.b64decode(encoded)
9.3 常见RSA攻击类型
除了本题的CRT参数泄露攻击,RSA还有其他常见攻击:
1. 小指数攻击
- 公钥指数e很小(如e=3)
- 明文很小,使得m^e < n
- 直接对密文开e次方根
2. 共模攻击
- 同一明文用不同公钥加密
- 但使用相同的模数n
- 利用扩展欧几里得算法恢复明文
3. Wiener攻击
- 私钥指数d很小
- d < n^0.25
- 利用连分数攻击
4. 费马分解
- p和q非常接近
- |p-q| 很小
- 可以快速分解n
5. Coppersmith攻击
- 已知部分明文
- 使用格基约化技术
- 可以恢复完整明文
9.4 调试技巧
在解题过程中的调试建议:
1. 逐步验证
# 每一步都打印中间结果
print(f"解码后长度: {len(data)}")
print(f"找到{len(integers)}个整数")
print(f"验证: {(d * e) % phi == 1}")
2. 异常处理
try:
result = some_operation()
except Exception as e:
print(f"错误: {e}")
print(f"当前状态: {current_state}")
3. 中间结果保存
# 保存中间结果,避免重复计算
with open('intermediate.txt', 'w') as f:
f.write(f"p = {p}\n")
f.write(f"q = {q}\n")
4. 使用日志
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug(f"尝试k={k}")
logger.info(f"找到质数p")
第十步:完整解题脚本
为了方便学习和复现,这里提供一个完整的自动化解题脚本:
#!/usr/bin/env python3
"""
RSA CRT参数泄露攻击 - 完整解题脚本
"""
import base64
from Crypto.Util.number import isPrime, bytes_to_long, long_to_bytes
print("=" * 70)
print("RSA CRT参数泄露攻击 - 自动化解题")
print("=" * 70)
print()
# 步骤1: Base64解码
print("[步骤1] Base64解码和DER解析")
print("-" * 70)
base64_fragment = "Os9mhOQRdqW2cwVrnNI72DLcAXpXUJ1HGwJBANWiJcDUGxZpnERxVw7s0913WXNtV4GqdxCzG0pG5EHThtoTRbyX0aqRP4U/hQ9tRoSoDmBn+3HPITsnbCy67VkCQBM4xZPTtUKM6Xi+16VTUnFVs9E4rqwIQCDAxn9UuVMBXlX2Cl0xOGUF4C5hItrX2woF7LVS5EizR63CyRcPovMCQQDVyNbcWD7N88MhZjujKuSrHJot7WcCaRmTGEIJ6TkU8NWt9BVjR4jVkZ2EqNd0KZWdQPukeynPcLlDEkIXyaQx"
data = base64.b64decode(base64_fragment)
print(f"Base64解码完成: {len(data)} 字节")
# 解析DER编码的INTEGER
integers = []
i = 0
while i < len(data):
if data[i] == 0x02:
length = data[i + 1]
if length <= 127:
value = int.from_bytes(data[i+2:i+2+length], byteorder='big')
integers.append(value)
i += 2 + length
else:
i += 1
else:
i += 1
print(f"提取INTEGER参数: {len(integers)} 个")
dp, dq, qinv = integers[-3], integers[-2], integers[-1]
print(f"dp = {dp}")
print(f"dq = {dq}")
print(f"qinv = {qinv}")
print()
# 步骤2: 恢复质数p和q
print("[步骤2] 从CRT参数恢复质数p和q")
print("-" * 70)
e = 65537
d1p = dp * e - 1
print(f"公钥指数 e = {e}")
print("开始遍历k值...")
p, q = None, None
for k in range(3, e):
if d1p % k == 0:
candidate_p = d1p // k + 1
if isPrime(candidate_p):
print(f"找到质数p (k={k})")
p = candidate_p
d1q = dq * e - 1
for m in range(3, e):
if d1q % m == 0:
candidate_q = d1q // m + 1
if isPrime(candidate_q):
q = candidate_q
if (qinv * q) % p == 1 or (qinv * p) % q == 1:
print(f"找到质数q (m={m})")
print(f"验证qinv关系: 通过")
break
if q:
break
if not p or not q:
print("错误: 无法恢复质数")
exit(1)
print(f"p = {p}")
print(f"q = {q}")
print()
# 步骤3: 计算私钥d
print("[步骤3] 计算RSA私钥d")
print("-" * 70)
n = p * q
phi = (p - 1) * (q - 1)
def egcd(a, b):
u, u1 = 1, 0
while b:
q = a // b
u, u1 = u1, u - q * u1
a, b = b, a - q * b
return u
d = egcd(e, phi)
if d < 0:
d += phi
print(f"n = {n}")
print(f"phi = {phi}")
print(f"d = {d}")
print(f"验证: (d*e) mod phi = {(d*e) % phi}")
print()
# 步骤4: RSA解密
print("[步骤4] RSA解密flag.enc")
print("-" * 70)
with open("flag.enc", "rb") as f:
ciphertext_bytes = f.read()
ciphertext = bytes_to_long(ciphertext_bytes)
print(f"密文长度: {len(ciphertext_bytes)} 字节")
plaintext = pow(ciphertext, d, n)
plaintext_bytes = long_to_bytes(plaintext)
print(f"RSA解密完成")
# 去除PKCS#1填充
separator = plaintext_bytes.find(b'\x00', 2)
if separator != -1:
message = plaintext_bytes[separator+1:].decode('utf-8').strip()
print(f"去除PKCS#1填充")
print()
print("=" * 70)
print("解密成功!")
print("=" * 70)
print(f"Flag: {message}")
print("=" * 70)
else:
print("错误: 未找到PKCS#1填充分隔符")
总结
这道题目是一个优秀的RSA密码分析案例,展示了:
- 密码学基础知识:RSA算法、ASN.1编码、PKCS#1填充
- 数学原理应用:模运算、欧几里得算法、素性测试
- 密码分析技巧:从部分信息恢复完整密钥
- 实践能力培养:编程实现、调试技巧、工具使用
通过深入分析这道题,我们不仅学会了如何解决这个特定问题,更重要的是掌握了密码分析的思维方法和技术手段。
密码学的安全性建立在数学难题之上,但实现中的任何细微疏忽都可能导致整个系统的崩溃。作为安全研究人员,我们需要:
- 深入理解密码算法的数学原理
- 关注实现细节和潜在的弱点
- 保持对新攻击技术的学习
- 在实践中不断提升分析能力
希望这份详细的技术解析能够帮助你深入理解RSA密码学及其安全性。在实际应用中,请务必遵循密码学最佳实践,使用经过充分测试的密码库,并时刻保持对安全的警惕。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:破镜安全 破镜安全 破镜安全《RSA密码学题目完全解析:从部分私钥片段到完整密钥恢复》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论