Decrypting TLS Traffic via SSLKEYLOGFILE Integration and Encrypted Session Reconstruction
Theory
Why This Matters
TLS encryption protects the vast majority of modern web traffic, API calls, and application protocols — and is the single most common reason a PCAP appears to contain nothing interesting beyond connection metadata. When a CTF challenge or investigation provides an SSLKEYLOGFILE alongside a PCAP, the entire encrypted conversation becomes readable. The SSLKEYLOGFILE format, standardised by NSS and supported by all major TLS implementations, captures the per-session key material needed to decrypt TLS 1.2 and TLS 1.3 traffic. Analysts who can load a key log file into Wireshark and navigate the decrypted content unlock the full forensic value of encrypted captures.
Core Concept
SSLKEYLOGFILE is a text file written by TLS client libraries (NSS, OpenSSL, BoringSSL, rustls) containing per-session key material in a standardised format. Each line is a space-separated triple:
<label> <client_random_hex> <secret_hex>
TLS 1.2 labels (most common):
- CLIENT_RANDOM <32-byte_client_random> <48-byte_master_secret> — the master secret from which all session keys are derived.
- RSA <encrypted_premaster_hex> <48-byte_premaster_secret> — for RSA key exchange (now rare).
TLS 1.3 labels (mandatory for TLS 1.3 — master_secret does not exist in 1.3):
- CLIENT_HANDSHAKE_TRAFFIC_SECRET <client_random> <secret> — decrypts client handshake messages.
- SERVER_HANDSHAKE_TRAFFIC_SECRET <client_random> <secret> — decrypts server handshake messages.
- CLIENT_TRAFFIC_SECRET_0 <client_random> <secret> — decrypts client application data (0-RTT: CLIENT_EARLY_TRAFFIC_SECRET).
- SERVER_TRAFFIC_SECRET_0 <client_random> <secret> — decrypts server application data.
Wireshark matches PCAP sessions to key log entries using the ClientHello.random field in the TLS handshake (visible even in encrypted PCAP) and the client_random in the key log file.
Technical Deep-Dive
# Wireshark GUI: load key log file
# Edit → Preferences → Protocols → TLS
# → (Pre)-Master-Secret log filename → browse to keylog.txt
# Then reload the PCAP: the dissector will now show decrypted HTTP/2 or HTTP
# tshark equivalent: -o flag to set TLS keylog preference
tshark -r capture.pcap
-o "tls.keylog_file:/path/to/keylog.txt"
-Y "http"
-T fields -e frame.number -e http.request.method
-e http.request.uri -e http.response.code
-E header=y
# Decrypt and follow a specific TLS stream to a readable stream
tshark -r capture.pcap
-o "tls.keylog_file:/path/to/keylog.txt"
-q -z "follow,tls,ascii,0"
# Export decrypted application data (all HTTP from decrypted TLS)
tshark -r capture.pcap
-o "tls.keylog_file:/path/to/keylog.txt"
-Y "http or http2"
-T fields
-e frame.number -e ip.src -e ip.dst
-e http.request.method -e http.request.uri
-e http.request.full_uri
-e http2.headers.path -e http2.headers.method
# Generate a SSLKEYLOGFILE from a live process (for reference):
# export SSLKEYLOGFILE=/tmp/keylog.txt
# curl https://example.com # NSS/OpenSSL will write keys
# firefox --no-remote & # Browser also honours SSLKEYLOGFILE
# Python ssl module: log keys from a Python TLS client
# (for understanding what the challenge artifact contains):
import ssl, os
ctx = ssl.create_default_context()
ctx.keylog_filename = "/tmp/python_keylog.txt"
# ctx then used in ssl.wrap_socket or http.client
# Verify that the key log file matches the PCAP:
# Step 1: extract all ClientHello randoms from PCAP
tshark -r capture.pcap
-Y "tls.handshake.type == 1"
-T fields -e tls.handshake.random
| sort -u > pcap_randoms.txt
# Step 2: extract client_random fields from keylog
grep -v "^#" keylog.txt | awk '{print $2}' | sort -u > keylog_randoms.txt
# Step 3: intersect — matching lines confirm the keylog covers this PCAP
comm -12 pcap_randoms.txt keylog_randoms.txt
#!/usr/bin/env python3
"""
Validate that a SSLKEYLOGFILE covers the TLS sessions in a PCAP.
Requires: pip install pyshark
"""
import pyshark, re
KEYLOG_FILE = "keylog.txt"
PCAP_FILE = "capture.pcap"
# Load client randoms from key log
keylog_randoms = set()
with open(KEYLOG_FILE) as fh:
for line in fh:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
if len(parts) >= 2:
keylog_randoms.add(parts[1].lower())
# Extract ClientHello randoms from PCAP
cap = pyshark.FileCapture(PCAP_FILE, display_filter="tls.handshake.type == 1")
covered, uncovered = 0, 0
for pkt in cap:
try:
rand = pkt.tls.handshake_random.replace(":", "").lower()
if rand in keylog_randoms:
covered += 1
else:
uncovered += 1
except AttributeError:
pass
cap.close()
print(f"TLS sessions covered by keylog : {covered}")
print(f"TLS sessions NOT in keylog : {uncovered}")
if uncovered:
print("WARNING: keylog does not cover all sessions in this PCAP.")
Analytical Methodology
- Confirm the PCAP contains TLS traffic by applying
tlsorsslfilter. Verify TLS versions present usingtls.record.versionfield. TLS 1.2 requiresCLIENT_RANDOMorRSAlabels; TLS 1.3 requiresCLIENT_HANDSHAKE_TRAFFIC_SECRETandCLIENT_TRAFFIC_SECRET_0. - Open the SSLKEYLOGFILE in a text editor. Verify it is non-empty and contains lines matching the expected label format. Count unique
client_randomvalues — this should match the number of distinct TLS sessions in the PCAP. - In Wireshark: Edit → Preferences → Protocols → TLS. Set the (Pre)-Master-Secret log filename field to the absolute path of the key log file. Click OK and reload the capture (Ctrl+Shift+F5 or File → Reload). TLS sessions that are now decrypted will show their inner protocol (HTTP, HTTP/2, SMTP, etc.) in the Protocol column.
- Apply the inner-protocol filter (
http,http2,smtp,ftp) to view only decrypted application data. Use Follow → TLS Stream on any decrypted frame to read the full plaintext exchange. - For TLS 1.3 captures, confirm that both
CLIENT_HANDSHAKE_TRAFFIC_SECRETandCLIENT_TRAFFIC_SECRET_0are present in the key log — both are needed to decrypt the full session (handshake alerts and application data respectively). - Export decrypted content using File → Export Objects → HTTP (for HTTP/1.1 over TLS) or use tshark with
-o "tls.keylog_file:..."to extract headers and bodies from HTTP/2 (gRPC, API traffic). - If decryption is partial (some sessions decode, others do not), use the validation script above to identify which ClientHello randoms are missing from the key log. Missing keys indicate sessions established before or after the key log capture window.
- Document: number of decrypted sessions, decrypted protocol distribution, key request URLs/commands, and any credentials or sensitive data visible in decrypted content.
Common Analytical Errors
- Loading the key log for TLS 1.3 but using only CLIENT_RANDOM entries:
CLIENT_RANDOMentries do not decrypt TLS 1.3 sessions. TLS 1.3 key derivation changed fundamentally; only the per-secret labels (CLIENT_HANDSHAKE_TRAFFIC_SECRET,CLIENT_TRAFFIC_SECRET_0, etc.) work. Confirm the key log contains TLS 1.3 labels. - Relative path in Wireshark TLS preference: Wireshark may not resolve relative paths correctly depending on working directory. Always use an absolute path for the key log file in Wireshark preferences.
- Not reloading the capture after setting the key log: Wireshark applies TLS decryption during dissection. If the key log path is set after the file is already open, a full reload is required — closing and reopening the PCAP file, not just refreshing.
- Confusing PCAP-NG enhanced encryption with SSLKEYLOGFILE: Wireshark 3.x+ supports embedding decryption secrets directly in PCAP-NG files as a decryption secrets block (DSB). If the challenge PCAP is in PCAP-NG format, check for embedded keys using
tshark -r capture.pcapng -z expertbefore loading a separate keylog file.
NICE Framework Alignment
| Code | Knowledge/Skill/Task Statement | How This Card Develops It |
|---|---|---|
| K0046 | Knowledge of intrusion detection systems and methodologies | TLS decryption enables IDS-level inspection of encrypted C2, exfiltration, and web attack traffic |
| K0093 | Knowledge of network protocols | TLS 1.2/1.3 handshake mechanics, key derivation, and the role of master/traffic secrets |
| K0221 | Knowledge of OSI model and network layers | TLS operates between layer 4 (TCP) and layer 7 (application); key log decryption reveals layer 7 content |
| S0046 | Skill in performing packet-level analysis | Loading SSLKEYLOGFILE into Wireshark and tshark, validating coverage, and extracting decrypted streams |
| T0023 | Collect intrusion artifacts for use in forensic analysis | Decrypted HTTP and API traffic are primary forensic artifacts for web attack and exfiltration investigations |
Further Reading
- NSS Key Log Format specification: firefox-source-docs.mozilla.org/security/nss/legacy/key_log_format
- Wireshark TLS decryption wiki: wiki.wireshark.org/TLS — SSLKEYLOGFILE setup and troubleshooting
- RFC 8446: TLS 1.3 — Section 7 (cryptographic computations) for understanding traffic secret derivation
- SANS: "Inspecting TLS Traffic with Wireshark" (FOR572 cheat sheet)
- Cloudflare blog: "TLS 1.3 0-RTT and Anti-Replay" — understanding EARLY_TRAFFIC_SECRET
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.