Wireshark Cheatsheet

TLS Decryption

Use this Wireshark reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Which Method Works

There are two ways to decrypt TLS in Wireshark and only one of them works on modern traffic.

MethodNeedsWorks on
Key log file (SSLKEYLOGFILE)Cooperation from the client or server processEverything, including TLS 1.3 and QUIC
RSA private keyThe server's private key fileOnly non-forward-secret ciphers, so RSA key exchange

Every current cipher suite uses ephemeral Diffie-Hellman (ECDHE), which is the point of forward secrecy: the session keys are never derivable from the long-term private key. TLS 1.3 removed static RSA key exchange entirely. So the private key method is a legacy path for old TLS 1.2 endpoints deliberately configured with TLS_RSA_WITH_* suites, and nothing else.

Decrypt only traffic you are authorized to decrypt. This is a debugging technique for systems you control.

Key Log File

The client writes secrets to a text file as it negotiates, and Wireshark reads them. Set SSLKEYLOGFILE in the environment of the process that makes the connection, before it starts.

export SSLKEYLOGFILE="$HOME/tls-keys.log"

# Browsers, Chrome and Firefox both honor it
google-chrome
firefox
open -a "Google Chrome"          # macOS, only if the var is exported first

# curl 7.58+, and anything built on OpenSSL 1.1.1+ or NSS
curl -sS https://example.com/ >/dev/null

# Node
NODE_OPTIONS="--tls-keylog=$HOME/tls-keys.log" node app.js

# Python, requests and urllib3 via OpenSSL honor SSLKEYLOGFILE from 3.8 on
python3 -c "import requests; requests.get('https://example.com')"

# Go needs code, there is no environment variable
#   tls.Config{KeyLogWriter: f}

Then point Wireshark at it:

Preferences → Protocols → TLS → (Pre)-Master-Secret log filename

Or from the command line, no GUI needed:

tshark -o "tls.keylog_file:$HOME/tls-keys.log" -r cap.pcapng -Y http
tshark -o "tls.keylog_file:$HOME/tls-keys.log" -r cap.pcapng \
  -z follow,tls,ascii,0 -q

The file is a plain text list, one secret per line. TLS 1.2 uses one label, TLS 1.3 uses four, two per direction plus the exporter and early-data secrets.

CLIENT_RANDOM <64 hex chars> <96 hex chars>
CLIENT_HANDSHAKE_TRAFFIC_SECRET <client random> <secret>
SERVER_HANDSHAKE_TRAFFIC_SECRET <client random> <secret>
CLIENT_TRAFFIC_SECRET_0 <client random> <secret>
SERVER_TRAFFIC_SECRET_0 <client random> <secret>

Same mechanism decrypts QUIC and HTTP/3, since QUIC derives its keys from the TLS 1.3 handshake and uses the same key log labels. No extra setting is needed.

RSA Private Key

For the legacy case, Preferences → RSA Keys → + (a top-level preferences page since Wireshark 3.0) takes a PEM or PKCS#12 key file. Older builds put the same thing under Protocols → TLS → RSA keys list, with an IP, port, protocol, and key path per row.

# Confirm the key matches the certificate before blaming Wireshark
openssl rsa  -in server.key -noout -modulus | openssl sha256
openssl x509 -in server.crt -noout -modulus | openssl sha256

It only decrypts when all of these hold:

- The cipher suite is RSA key exchange, not ECDHE or DHE. Check tls.handshake.ciphersuite on the ServerHello and look for a name starting TLS_RSA_WITH_. - The capture contains the full handshake, starting at the ClientHello. A capture that begins mid-session has no key exchange to unwrap. - Session resumption did not skip the key exchange. An abbreviated handshake with a session ticket carries no encrypted premaster secret. - The key is the one for that certificate, unencrypted or with the passphrase supplied.

Embed Secrets in the pcapng

pcapng can carry decryption secrets as a block inside the capture file, so the file decrypts on any machine with no side-channel key log. This is how you hand a TLS capture to a colleague.

# Bake a key log into the capture
editcap --inject-secrets tls,tls-keys.log cap.pcapng cap-with-keys.pcapng

# What can be injected
editcap --inject-secrets help

# Pull the secrets back out of a capture that already has them
tshark -r cap-with-keys.pcapng --export-tls-session-keys out.log

In the GUI, File → Export TLS Session Keys writes the same file, and Edit → Inject TLS Secrets embeds the currently loaded keys into the open capture. Edit → Discard All Secrets strips them again before sharing a file you should not have decrypted for someone else.

Verify and Troubleshoot

When decryption is set up, the packet list Protocol column changes from TLSv1.3 to HTTP2 or HTTP, a Decrypted TLS tab appears in the byte pane, and Follow → TLS Stream shows plaintext.

tls.handshake.type == 1                 # ClientHello, is the handshake captured
tls.handshake.type == 2                 # ServerHello, which suite was chosen
tls.handshake.ciphersuite               # add as a column
tls.record.content_type == 23           # application data, the encrypted bulk
tls.record.content_type == 21           # alert
tls.alert_message.desc                  # why it failed
http2 and tls                           # proof that decryption worked
SymptomCause
Still shows Application DataNo matching CLIENT_RANDOM line, or the key log was written after the capture started
Some streams decrypt, others do notKey log missing entries for connections opened before SSLKEYLOGFILE was set
Nothing decrypts with an RSA keyForward-secret cipher suite. Check tls.handshake.ciphersuite
Handshake present, decryption failsCapture missing the start of the session, or a resumed session
Browser writes no key logVariable not exported to that process. A browser already running does not pick it up
Certificates not visible in TLS 1.3Correct, they are encrypted. Decrypt first, then look

Common TLS alert descriptions, since tls.alert_message.desc is a number:

ValueAlert
0close_notify, a clean shutdown
40handshake_failure, usually no shared cipher or version
42bad_certificate
46certificate_unknown
47illegal_parameter
48unknown_ca, the classic self-signed or missing-intermediate error
49access_denied
51decrypt_error, often a client-certificate signature problem
70protocol_version, one side refused the offered version
112unrecognized_name, SNI does not match a configured server