文章总结: 本文详解CTF中RSA加密流量分析与解密的实战流程。作者通过Wireshark提取TCP流,识别Base64编码结构,利用小模数RSA的脆弱性分解素数并计算私钥,最终解密数据验证签名以获取flag。内容涵盖流量分析、RSA数学原理及脚本编写,强调密钥长度对安全的重要性,适合密码学初学者学习。 综合评分: 100 文章分类: CTF,漏洞分析,数据安全,实战经验,安全工具
CTF密码学实战:RSA加密流量分析与解密完全指南
原创
破镜安全 破镜安全
破镜安全
2026年1月23日 08:00 四川
CTF密码学实战:RSA加密流量分析与解密完全指南
前言
在CTF竞赛中,密码学题目往往结合多个知识点,既考察对加密算法的理解,也考验实际的分析能力。本文将通过ISCC 2017的一道真实题目,完整展示如何从网络流量包中提取加密数据,分析加密方式,最终通过RSA密码学知识破解并获取flag。
本文适合密码学初学者阅读,会详细解释每一步操作的原理和目的。
题目背景
拿到题目后,我们得到一个文件:Basic-06.pcapng。从文件扩展名可以看出,这是一个网络数据包捕获文件,通常由Wireshark或tcpdump等工具生成。
题目名称为”说我作弊需要证据”,这个略带挑衅的名字暗示我们需要从流量中找到”证据”——也就是隐藏在网络通信中的秘密信息。
第一步:流量文件初步分析
1.1 为什么要分析流量文件?
在现实场景中,很多敏感信息会通过网络传输,即使使用了加密,但如果加密方式存在漏洞或密钥强度不够,仍然可能被破解。这道题模拟的就是这样一个场景:攻击者截获了一段加密通信,需要尝试解密获取明文。
1.2 使用Wireshark/tshark分析
首先使用tshark(Wireshark的命令行版本)查看网络流量的基本信息:
tshark -r Basic-06.pcapng -q -z conv,tcp
执行后得到TCP会话统计信息:
TCP Conversations
192.168.0.13:31337 <-> 192.168.0.37:31338
发送: 150包 10KB
接收: 152包 28KB
总计: 302包 38KB
分析结果:
- 存在一个TCP连接,从192.168.0.13的31337端口到192.168.0.37的31338端口
- 通信持续约16.5秒
- 数据传输方向主要是192.168.0.13向192.168.0.37发送数据
这说明192.168.0.13是数据发送方(我们称为Alice),192.168.0.37是接收方(我们称为Bob)。
1.3 提取TCP流数据
接下来提取完整的TCP流内容:
tshark -r Basic-06.pcapng -q -z follow,tcp,ascii,0 > tcp_stream.txt
这个命令会把TCP会话中的所有数据按顺序提取出来,保存为文本文件。
打开tcp_stream.txt后,我们看到大量类似这样的内容:
121
U0VRID0gMTM7IERBVEEgPSAweD...
117
U0VRID0gMDsgREFUQSA9IDB4N...
每段数据前面有一个数字(表示数据长度),然后是一串由大小写字母、数字、加号和等号组成的字符串。
第二步:识别编码方式
2.1 Base64编码特征识别
看到这些字符串,有经验的安全研究人员会立即联想到Base64编码。Base64是一种常见的二进制到文本的编码方案,特征非常明显:
Base64编码特征:
- 字符集:A-Z, a-z, 0-9, +, /(共64个字符)
- 填充字符:使用=或==作为结尾(使长度成为4的倍数)
- 不含特殊字符(除了+、/、=)
- 编码后长度约为原始数据的4/3倍
2.2 Base64解码
让我们尝试解码第一行数据:
import base64
data = "U0VRID0gMTM7IERBVEEgPSAweD..."
decoded = base64.b64decode(data)
print(decoded.decode('utf-8'))
解码结果:
SEQ = 13; DATA = 0x3b04b26a0adada2f67326bb0c5d6L; SIG = 0x2e5ab24f9dc21df406a87de0b3b4L;
太好了!解码成功,并且得到了结构化的数据。
2.3 数据结构分析
仔细观察解码后的内容,可以发现三个关键字段:
- SEQ(Sequence):序列号,值为整数(如13)
- DATA:以0x开头的十六进制大整数,末尾有L(Python 2中长整型标记)
- SIG(Signature):同样是0x开头的十六进制大整数
这种结构强烈暗示:
- SEQ用于标识数据包顺序
- DATA可能是加密后的密文
- SIG可能是数字签名
第三步:批量提取和解析数据
3.1 编写提取脚本
我们需要从TCP流文件中提取所有base64数据并解码:
import base64
import re
# 读取TCP流文件
with open('tcp_stream.txt', 'r') as f:
content = f.read()
# 使用正则表达式提取所有base64行
base64_lines = re.findall(r'^[A-Za-z0-9+/=]+$', content, re.MULTILINE)
print(f"找到 {len(base64_lines)} 行base64数据")
运行结果显示找到了298行base64数据。
3.2 解析数据包
对每一行进行解码和字段提取:
packets = []
for line in base64_lines:
try:
# Base64解码
decoded = base64.b64decode(line).decode('utf-8', errors='ignore')
# 提取SEQ字段
seq_match = re.search(r'SEQ\s*=\s*(\d+)', decoded)
# 提取DATA字段
data_match = re.search(r'DATA\s*=\s*(0x[0-9a-fA-F]+)', decoded)
# 提取SIG字段
sig_match = re.search(r'SIG\s*=\s*(0x[0-9a-fA-F]+)', decoded)
if seq_match and data_match and sig_match:
packets.append({
'seq': int(seq_match.group(1)),
'data': data_match.group(1),
'sig': sig_match.group(1)
})
except:
pass
print(f"成功解析 {len(packets)} 个有效数据包")
运行结果:成功解析了148个有效数据包。
3.3 数据包统计分析
对解析出的数据包进行统计:
# 统计SEQ分布
seq_set = set([p['seq'] for p in packets])
print(f"SEQ范围: {min(seq_set)} - {max(seq_set)}")
print(f"唯一SEQ数量: {len(seq_set)}")
结果显示:
- SEQ范围:0到33
- 共34个不同的序列号
- 总共148个数据包,说明有些SEQ有多个数据包(重复发送或干扰数据)
这个发现很重要:最终的消息可能只有34个字符,而148个包中大部分是重复或干扰数据。
第四步:识别加密算法
4.1 从数据特征判断
观察DATA和SIG字段:
- 都是非常大的整数(100位左右)
- 每个包的DATA和SIG都不同
- SEQ相同的包,DATA和SIG也不同
这些特征强烈暗示使用了RSA加密:
RSA的特点:
- 处理大整数(通常几百到几千位)
- 密文长度与明文相关,但相近明文会产生完全不同的密文
- 常与数字签名一起使用
4.2 RSA加密通信模型
在RSA加密通信中,典型的流程是:
发送方(Alice) 接收方(Bob)
| |
| 生成消息M |
| |
| 用Alice私钥签名 |
| SIG = M^d_alice mod n_alice
| |
| 用Bob公钥加密 |
| DATA = M^e_bob mod n_bob |
| |
|----[DATA, SIG]---------->|
| |
| | 用Bob私钥解密
| | M = DATA^d_bob mod n_bob
| |
| | 用Alice公钥验证签名
| | M ?= SIG^e_alice mod n_alice
这样设计的目的:
- 加密(DATA):保证数据机密性,只有Bob能解密
- 签名(SIG):保证数据真实性,证明确实来自Alice且未被篡改
第五步:RSA密码学基础
在尝试破解之前,我们需要理解RSA的数学原理。
5.1 RSA算法核心概念
密钥生成:
- 选择两个大素数p和q
- 计算n = p × q(RSA模数)
- 计算φ(n) = (p-1) × (q-1)(欧拉函数)
- 选择公钥指数e(常用65537,即0x10001)
- 计算私钥指数d,满足:e × d ≡ 1 (mod φ(n))
加密解密:
- 加密:C = M^e mod n
- 解密:M = C^d mod n
数学原理:根据欧拉定理,当gcd(M, n) = 1时:
M^φ(n) ≡ 1 (mod n)
因此:
C^d = (M^e)^d = M^(ed) = M^(1+k×φ(n)) = M × (M^φ(n))^k ≡ M × 1^k ≡ M (mod n)
5.2 RSA安全性基础
RSA的安全性依赖于:
- 大整数分解难题:已知n,很难分解出p和q
- 如果n足够大(如2048位),分解几乎不可能
但是,如果n太小(如100位),就可以被分解!
5.3 模逆运算
计算私钥d需要求e在模φ(n)下的逆元,使用扩展欧几里得算法:
def iterative_egcd(a, b):
"""扩展欧几里得算法
返回 (gcd, x, y) 使得 a*x + b*y = gcd(a,b)
"""
x, y, u, v = 0, 1, 1, 0
while a != 0:
q, r = b // a, b % a # q是商,r是余数
m, n = x - u * q, y - v * q
b, a, x, y, u, v = a, r, u, v, m, n
return b, x, y
def modinv(a, m):
"""计算a在模m下的逆元
即找到x使得 a*x ≡ 1 (mod m)
"""
g, x, y = iterative_egcd(a, m)
if g != 1:
return None # 不存在逆元
return x % m
算法原理:扩展欧几里得算法在计算gcd(a,b)的同时,找到x和y使得ax + by = gcd(a,b)。当gcd(a,m) = 1时,ax + my = 1,两边对m取模得到ax ≡ 1 (mod m),因此x就是a的模逆。
第六步:获取RSA参数
6.1 观察密文大小
我们先看看DATA字段的大小:
# 取第一个数据包的DATA
data_hex = "0x3b04b26a0adada2f67326bb0c5d6"
data_int = int(data_hex, 16)
print(f"DATA整数值: {data_int}")
print(f"位数: {len(bin(data_int)) - 2} bits")
发现密文大约是100位,这意味着RSA模数n也大约是100位。
关键发现:100位的RSA模数太小了,可以被分解!
6.2 分解RSA模数
对于小的RSA模数,可以使用在线工具(如factordb.com)或Yafu等工具分解。
在CTF题目中,通常会提供或能够找到分解后的素数。经过分析和查找,我们得到:
Bob的RSA参数(用于解密DATA):
B_p = 49662237675630289
B_q = 62515288803124247
B_n = B_p * B_q
# n = 3104649130901425335933838103517383
验证:
print(B_n == 3104649130901425335933838103517383) # True
Alice的RSA参数(用于验证签名):
A_p = 38456719616722997
A_q = 44106885765559411
A_n = A_p * A_q
# n = 1696206139052948924304948333474767
6.3 为什么需要两组参数?
在实际的RSA通信中:
- Bob有自己的密钥对(n_bob, e_bob, d_bob),用于接收加密消息
- Alice有自己的密钥对(n_alice, e_alice, d_alice),用于生成数字签名
因此:
- DATA是用Bob的公钥加密的,需要Bob的私钥解密
- SIG是用Alice的私钥签名的,需要Alice的公钥验证
6.4 公钥指数e
RSA中的公钥指数e通常使用标准值:
e = 0x10001 # 十六进制,等于十进制的65537
为什么选择65537?
- 是素数,保证与φ(n)互质
- 二进制表示为10000000000000001,只有两个1
- 模幂运算时可以优化,计算速度快
- 经过长期实践验证,是安全且高效的选择
第七步:计算私钥
7.1 计算欧拉函数
有了p和q,可以计算欧拉函数:
B_phi = (B_p - 1) * (B_q - 1)
print(f"φ(n) = {B_phi}")
# φ(n) = 3104649130901425223756311624762848
欧拉函数的意义:φ(n)表示小于n且与n互质的正整数的个数。对于n = p×q(p、q为素数),φ(n) = (p-1)(q-1)。
7.2 计算私钥d
使用模逆运算计算私钥:
d = modinv(e, B_phi)
print(f"私钥 d = {d}")
# d = 1427000713644866747260499795119265
7.3 验证私钥正确性
验证e和d是否满足关系:
verification = (e * d) % B_phi
print(f"(e × d) mod φ(n) = {verification}")
# 应该等于1
如果结果是1,说明私钥计算正确!
为什么要满足这个关系?因为在解密时:
M = C^d = (M^e)^d = M^(ed) mod n
要使M^(ed) ≡ M (mod n),根据欧拉定理,需要ed ≡ 1 (mod φ(n))。
第八步:解密数据
8.1 解密原理
现在我们有了:
- Bob的私钥d
- Bob的模数n
- 148个加密的数据包
解密公式:
M = DATA^d mod n
使用Python的内置pow函数,支持模幂运算:
plaintext = pow(ciphertext, d, n)
8.2 签名验证原理
同时需要验证签名,确保数据未被篡改:
M_verify = SIG^e mod n_alice
如果M == M_verify,说明:
- 签名确实是Alice用私钥生成的
- 数据在传输过程中没有被修改
数字签名的数学原理:Alice生成签名时:SIG = M^d_alice mod n_alice Bob验证时:SIG^e_alice = (M^d_alice)^e_alice = M^(d×e) ≡ M (mod n_alice)
8.3 编写解密代码
A_n = 1696206139052948924304948333474767
e = 0x10001
decrypted_messages = []
for packet in packets:
# 将十六进制字符串转为整数
data_ciphertext = int(packet['data'], 16)
sig_value = int(packet['sig'], 16)
# 使用Bob私钥解密DATA
plaintext = pow(data_ciphertext, d, B_n)
# 使用Alice公钥验证签名
sig_verified = pow(sig_value, e, A_n)
# 只保留签名验证通过的数据
if plaintext == sig_verified:
decrypted_messages.append({
'seq': packet['seq'],
'value': plaintext,
'char': chr(plaintext) if plaintext < 256 else '?'
})
print(f"成功解密并验证 {len(decrypted_messages)} 个字符")
8.4 解密结果分析
运行后发现:
- 总数据包:148个
- 验证通过:33个
- 验证通过率:22.3%
这说明148个包中有很多是干扰数据或重复数据,只有33个包含有效信息。
查看解密出的字符:
# 按SEQ排序
decrypted_messages.sort(key=lambda x: x['seq'])
# 查看前10个字符
for msg in decrypted_messages[:10]:
print(f"SEQ {msg['seq']:2d}: ASCII {msg['value']:3d} = '{msg['char']}'")
输出:
SEQ 0: ASCII 102 = 'f'
SEQ 1: ASCII 108 = 'l'
SEQ 2: ASCII 97 = 'a'
SEQ 3: ASCII 103 = 'g'
SEQ 4: ASCII 123 = '{'
SEQ 5: ASCII 110 = 'n'
SEQ 6: ASCII 48 = '0'
SEQ 7: ASCII 116 = 't'
SEQ 8: ASCII 104 = 'h'
SEQ 9: ASCII 49 = '1'
看起来像是flag的开头!
第九步:组装最终flag
9.1 按序组装
将所有解密的字符按SEQ顺序拼接:
flag = ''.join([msg['char'] for msg in decrypted_messages])
print(f"Flag: {flag}")
9.2 最终结果
flag{n0th1ng_t0_533_h3r3_m0v3_0n}
成功!这就是题目的flag。
Flag含义:“nothing to see here move on” – 这里没什么可看的,继续前进吧
这是一个常见的CTF幽默风格的flag,暗示题目已经解决。
完整解题脚本
将所有步骤整合成完整的脚本:
# coding=utf-8
"""
RSA流量分析解密脚本
"""
import base64
import re
def iterative_egcd(a, b):
"""扩展欧几里得算法"""
x, y, u, v = 0, 1, 1, 0
while a != 0:
q, r = b // a, b % a
m, n = x - u * q, y - v * q
b, a, x, y, u, v = a, r, u, v, m, n
return b, x, y
def modinv(a, m):
"""计算模逆"""
g, x, y = iterative_egcd(a, m)
if g != 1:
return None
return x % m
def main():
print("=" * 60)
print("RSA流量分析解密")
print("=" * 60)
# 步骤1: 读取并解析TCP流
with open('tcp_stream.txt', 'r') as f:
content = f.read()
# 步骤2: 提取base64数据
base64_lines = re.findall(r'^[A-Za-z0-9+/=]+$', content, re.MULTILINE)
print(f"\n[*] 找到 {len(base64_lines)} 行base64数据")
# 步骤3: 解码并解析
packets = []
for line in base64_lines:
try:
decoded = base64.b64decode(line).decode('utf-8', errors='ignore')
seq_match = re.search(r'SEQ\s*=\s*(\d+)', decoded)
data_match = re.search(r'DATA\s*=\s*(0x[0-9a-fA-F]+)', decoded)
sig_match = re.search(r'SIG\s*=\s*(0x[0-9a-fA-F]+)', decoded)
if seq_match and data_match and sig_match:
packets.append({
'seq': int(seq_match.group(1)),
'data': data_match.group(1),
'sig': sig_match.group(1)
})
except:
pass
print(f"[*] 解析出 {len(packets)} 个有效数据包")
# 步骤4: 设置RSA参数
B_p = 49662237675630289
B_q = 62515288803124247
B_n = 3104649130901425335933838103517383
B_phi = (B_p - 1) * (B_q - 1)
A_n = 1696206139052948924304948333474767
e = 0x10001
# 步骤5: 计算私钥
d = modinv(e, B_phi)
print(f"[*] 计算私钥: d = {d}")
# 步骤6: 解密并验证
decrypted = []
for packet in packets:
data_c = int(packet['data'], 16)
data_m = pow(data_c, d, B_n)
sig_c = int(packet['sig'], 16)
sig_m = pow(sig_c, e, A_n)
if data_m == sig_m:
decrypted.append({
'seq': packet['seq'],
'char': chr(data_m)
})
print(f"[*] 成功解密 {len(decrypted)} 个字符")
# 步骤7: 组装flag
decrypted.sort(key=lambda x: x['seq'])
flag = ''.join([x['char'] for x in decrypted])
print("\n" + "=" * 60)
print(f"Flag: {flag}")
print("=" * 60)
if __name__ == '__main__':
main()
技术总结
核心知识点
通过这道题,我们学习了以下核心技术:
1. 网络流量分析
- 使用Wireshark/tshark提取TCP流数据
- 识别和分析网络通信模式
- 从数据包中提取有用信息
2. 编码识别与解码
- Base64编码的特征识别
- 使用Python进行批量解码
- 正则表达式提取结构化数据
3. RSA密码学
- RSA加密解密的数学原理
- 欧拉定理和模运算
- 公钥加密与私钥解密的对应关系
4. 数字签名
- 数字签名的生成与验证原理
- 签名如何保证数据完整性和真实性
- 加密与签名的组合使用
5. 密码分析
- 小模数RSA的脆弱性
- 大整数分解攻击
- 如何计算RSA私钥
6. Python密码学编程
- 扩展欧几里得算法实现
- 模逆运算
- 大整数的模幂运算
关键技术细节
为什么可以破解这个RSA?
- RSA模数只有约100位,太小
- 使用工具可以分解出p和q
- 有了p和q就能计算φ(n)
- 从φ(n)和e可以计算出d
- 有了私钥d就能解密所有数据
为什么需要签名验证?
- 148个数据包中混有干扰数据
- 签名验证可以过滤出真实数据
- 确保数据来自正确的发送方
- 确保数据在传输中未被篡改
为什么SEQ会重复?
- 可能是网络重传机制
- 可能是故意添加的干扰数据
- 通过签名验证可以识别有效数据
- 每个SEQ只需要保留一个有效字符
安全启示
这道题给我们的安全启示:
1. 密钥长度至关重要
- 100位RSA模数极不安全
- 实际应用中应使用至少2048位
- 更高安全需求应使用4096位
2. 流量加密不是万能的
- 即使使用加密,弱密钥仍可能被破解
- 需要结合多种安全措施
- 定期更新密钥和算法
3. 数字签名的重要性
- 签名能够验证数据来源
- 能够检测数据是否被篡改
- 加密和签名应配合使用
4. 安全实践建议
- 使用标准的密码库(如OpenSSL)
- 不要自己实现密码算法
- 保持密码学知识更新
- 定期进行安全审计
进阶学习建议
如果你对密码学感兴趣,建议按以下路线深入学习:
基础数学:
- 数论基础(素数、同余、模运算)
- 群论、环论基础
- 欧拉定理、费马小定理
- 中国剩余定理
密码学算法:
- 对称加密(AES、DES、ChaCha20)
- 非对称加密(RSA、ECC、ElGamal)
- 哈希函数(SHA系列、MD5)
- 消息认证码(HMAC)
密码分析:
- RSA攻击方法(小指数攻击、共模攻击、侧信道攻击)
- 分组密码分析(差分分析、线性分析)
- 哈希碰撞攻击
- 时序攻击和能量分析
实践工具:
- Python密码学库(pycryptodome、cryptography)
- SageMath数学计算系统
- OpenSSL命令行工具
- RSATool、yafu等专用工具
推荐资源:
- 书籍:《应用密码学》(Bruce Schneier)
- 在线课程:Coursera的密码学课程(斯坦福大学)
- CTF平台:CryptoHack、cryptopals
- 论文:经典密码学论文阅读
结语
这道题目综合考察了网络流量分析、编码识别、RSA密码学、数字签名等多个知识点,是一道非常优秀的CTF密码学题目。通过实际动手解决这类问题,我们不仅能够理解密码学理论,更能够培养实际的安全分析能力。
密码学是信息安全的基石,理解其原理对于从事安全工作至关重要。希望通过本文的详细讲解,能够帮助初学者建立起对RSA加密、数字签名和密码分析的基本认识。
在实际工作中,我们要始终记住:安全不是一个点,而是一个系统。即使使用了加密技术,如果密钥管理不当、算法选择错误或参数设置不合理,仍然会导致安全问题。只有深入理解密码学原理,才能在实践中正确使用密码技术,保护信息安全。
继续学习,不断实践,你也能够成为密码学和信息安全领域的专家。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:破镜安全 破镜安全 破镜安全《CTF密码学实战:RSA加密流量分析与解密完全指南》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论