通过Scapy结合专属私钥解密TLS1.3 HTTPS流量的技术问询
问题说明
- 需求:在自有服务器上使用Scapy解密归属自有域名的HTTPS流量,已持有该域名正规签发的TLS1.3证书及对应私钥。
- 已验证无效方案:scapy-ssl_tls、Scapy官方TLS解析示例notebook、TSHARK、各类MITM方案、SSLDUMP,以上方案运行无报错、无崩溃,但无法完成解密。
- 现有报错:自行编写的测试代码运行抛出
ValueError: ciphertext length must be equal to key size错误,调试后未定位根因与修复方案,测试代码如下:
import os, sys import ssl from scapy import * from scapy.all import * from scapy.layers import * from scapy.layers.tls import * from scapy.layers import inet from scapy.layers.inet import * import socket, select import binascii import io from io import StringIO load_layer("tls") c = Cert("SSL/STAR_mydomain_com.crt") k=PrivKey("SSL/private.key") ########### Unit-Test Encrypt/Decrypt Run OK ########### #M = bytes("message to be encrypted".encode()) #enc = c.encrypt(M, t='oaep') #print(enc) #dec = k.decrypt(enc, t='oaep') #print(dec) while True: s = sniff(filter="port 443", count=10) ch_list = [p for p in s] if len(ch_list)>0: for pkt in ch_list: p_layer = pkt.getlayer('IP') src = p_layer.src dst = p_layer.dst if (src=='my-server-ip') or (dst=='my-server-ip'): if ('TLSEncryptedContent' in str(type(pkt['TCP'].payload))): ########### any 3 below commands go in same error ############## ########### and i was try 'pss' instead of 'oaep' but same error dec2 = k.decrypt(pkt['TCP'].payload, t='oaep') #dec2 = k.decrypt(bytes(pkt['TCP'].payload), t='oaep') #dec2 = k.decrypt(bytes(pkt['TCP'].payload).decode('UTF8', 'replace'), t='oaep') print(dec2) else: print('...passed')
注:不需要TLS1.3解密原理性解释,已知该能力可实现(如Palo Alto PAN-OS已支持TLS1.3流量解密),仅需可落地的代码片段或参考实现。
错误原因
原有代码逻辑错误:直接使用RSA私钥解密TLSEncryptedContent应用层载荷。TLS1.3应用数据由握手阶段派生的对称会话密钥加密,RSA私钥仅用于握手阶段的签名验证(TLS1.3默认ECDHE密钥交换场景)或解密预主密钥(仅旧版RSA密钥交换场景使用,TLS1.3已废弃),无法直接解密应用层加密内容,因此触发密文长度与密钥长度不匹配错误。
可落地实现代码
前置依赖
- 安装Scapy版本 >= 2.5.0,旧版本TLS1.3解密逻辑存在缺陷
- 抓包动作必须早于TLS连接建立时间,需捕获完整的握手流程(Client Hello到Server Finished全量报文),缺失握手报文无法派生会话密钥
from scapy.all import * from scapy.layers.tls import TLS, TLSApplicationData load_layer("tls") # 配置参数 SERVER_IP = "替换为你的服务器公网IP" CERT_PATH = "SSL/STAR_mydomain_com.crt" PRIV_KEY_PATH = "SSL/private.key" LISTEN_FILTER = f"tcp port 443 and host {SERVER_IP}" # 加载服务端证书与私钥 server_cert = Cert(CERT_PATH) server_privkey = PrivKey(PRIV_KEY_PATH) def parse_tls_pkt(pkt): if IP not in pkt or TCP not in pkt: return # 过滤非本机收发的流量 if pkt[IP].src != SERVER_IP and pkt[IP].dst != SERVER_IP: return try: # 传入服务端密钥材料,由Scapy自动完成握手解析、会话密钥派生、流量解密 decrypted_tls = pkt[TLS](server_key=server_privkey, server_cert=server_cert) # 输出解密后的应用层明文 if TLSApplicationData in decrypted_tls: src_info = f"{pkt[IP].src}:{pkt[TCP].sport}" dst_info = f"{pkt[IP].dst}:{pkt[TCP].dport}" print(f"[{src_info} -> {dst_info}] 解密明文:") print(decrypted_tls[TLSApplicationData].data) print("-" * 60) except Exception: # 自动跳过重传包、分片包、不完整TLS包 pass if __name__ == "__main__": print("开始监听443端口TLS流量,捕获完整握手后将自动输出解密明文...") sniff( filter=LISTEN_FILTER, prn=parse_tls_pkt, session=TLSSession # 启用TLS会话跟踪,自动维护跨包的会话状态 )
运行说明
- 代码默认支持TLS1.3 1-RTT模式流量解密,若服务端开启0-RTT,需在TLS初始化参数中补充0-RTT密钥配置
- TLS1.3默认使用ECDHE密钥交换,代码会自动通过私钥验证握手签名,结合客户端/服务器随机数、ECDHE公钥材料派生对称会话密钥,无需额外配置
- 若解密失败,优先确认是否捕获了完整握手流程,其次确认证书与私钥匹配、Scapy版本符合要求。
内容的提问来源于stack exchange,提问作者Eyni Kave
相关产品推荐
相关产品推荐

