Decrypt HTTPS and LDAPS Traffic in Wireshark with SSLKEYLOGFILE

Tested on RHEL 10.2 (Coughlan)
Package Wireshark 4.4.2
OpenSSL 3.5.5
curl 8.12.1
openldap-clients 2.6.10
tcpdump 4.99.4
Applies to Wireshark 4.x on Linux, Windows, and macOS. The capture commands below are Linux.
Privilege Root or sudo to capture packets. A normal user can open the PCAP and load the key log.
Scope How to decrypt HTTPS and LDAPS in Wireshark with a TLS key log, which clients actually write SSLKEYLOGFILE, and why a capture stays encrypted when the secrets are missing or from another session. Does not cover building a certificate authority or decrypting with an RSA server private key.
Related guides Analyze TLS and mTLS in Wireshark
Analyze HTTP responses in Wireshark
Analyze LDAP traffic in Wireshark
Configure OpenLDAP with TLS
Wireshark tutorial
Short on time?Beta Get the gist of this 12-minute read in seconds, then ask follow-up questions

Use a TLS key log to read HTTPS and LDAPS in Wireshark. The same session secrets turn encrypted application data into HTTP for one capture and into LDAP bind and search messages for the other.

The key log is not produced by Wireshark, and setting SSLKEYLOGFILE does not make every program log keys. The client that creates the TLS connection has to be built with a library that writes those secrets, and the capture has to contain that same handshake.


Key takeaways

  • Wireshark cannot decrypt modern TLS from the PCAP alone. You need the per-session TLS secrets from the same connection.
  • SSLKEYLOGFILE works only when that client’s TLS stack supports key logging.
  • A TLS key log works with TLS 1.3 and ephemeral key exchange. The legacy method of loading an RSA server private key does not.
  • The PCAP and the key log must belong to the same TLS session. Start the capture before opening a new connection so the PCAP includes the TLS handshake.
  • After decryption, HTTPS is dissected as HTTP or HTTP/2, and LDAPS becomes LDAP fields such as bindRequest and searchRequest.
  • Treat the key log as sensitive. Anyone with both the capture and the matching secrets can read the decrypted traffic.

What you need to decrypt HTTPS or LDAPS

You need two files from the same connection:

  • A PCAP that includes the TLS handshake, not only later application data.
  • A key log written by a program that took part in that connection.

Wireshark then uses the secrets to decrypt that session. A key log from an unrelated TLS session does not decrypt the packets. TLS and mTLS handshakes in Wireshark shows why TLS 1.3 hides the handshake after ServerHello until those secrets are loaded.

Client
├── HTTPS request
└── LDAPS search
        |
     TLS 1.3
        |
        v
   Test server

Both clients talk TLS 1.3 to a test server. The application on top is HTTP in one capture and LDAP in the other. The decryption step is the same.


How TLS session-key logging works

During the handshake the two sides derive session keys. With TLS 1.3 and with Diffie-Hellman key exchange, those keys are not encrypted to the server’s RSA public key, so the server private key cannot recover them later. A key log is a text file of the secrets from that handshake. Each line starts with a label such as SERVER_HANDSHAKE_TRAFFIC_SECRET or CLIENT_TRAFFIC_SECRET_0, then a client random, then the secret.

SSLKEYLOGFILE is the environment variable many TLS stacks check for the path of that file. Wireshark reads it from Edit, Preferences, Protocols, TLS, (Pre)-Master-Secret log filename. The command-line name of the same preference is tls.keylog_file.

Keep the key log private. Anyone who has the key log and the matching capture can decrypt those sessions. Do not attach the raw file to a ticket or commit it.


Does SSLKEYLOGFILE work with my application?

Check which TLS library the client actually uses before you trust an empty or a missing key log. ldd $(command -v ldapsearch) and curl -V show the library. On this host, curl -V reports OpenSSL 3.5.5, and ldapsearch is linked to libssl.so.3.

Client or TLS stack What to expect
Firefox or Chromium Writes a key log when SSLKEYLOGFILE is set before the browser starts. Quit an already running browser first.
curl with OpenSSL 3.4 or later Setting the variable on the command writes the file. Confirm the file grows. This curl is 8.12.1 built with OpenSSL 3.5.5.
GnuTLS The library honors SSLKEYLOGFILE.
OpenSSL 3.4 and later libssl writes the file for programs that use it. ldapsearch on this host did.
OpenSSL older than 3.4 The variable alone does nothing unless that application registers key logging. The file stays empty.
A capture from someone else’s machine You cannot decrypt it later unless you also have the secrets from that session.

A program linked to an older OpenSSL, or started before the variable was set, produces the failure people report: the path exists, the connection succeeded, and the key log is empty.


Capture environment

The screenshots use a local TLS 1.3 server so the HTTPS and LDAPS captures are easy to compare. Building the certificates is covered in create a client and server certificate. Standing up LDAP with TLS is covered in configure OpenLDAP with TLS.

Piece In these captures
Wireshark 4.4.2
TLS library OpenSSL 3.5.5
HTTPS curl to 127.0.0.1:18443
LDAPS ldapsearch to 127.0.0.1:636
Key log SSLKEYLOGFILE on the client command

Your addresses and ports will differ. The messages to look for are the HTTP request after decryption, and bindRequest plus searchRequest for LDAPS.


Capture HTTPS and a matching key log

Start the capture before the request, and start the client with the key log variable set. Leave tcpdump running:

bash
sudo tcpdump -i lo -U -w https.pcap port 18443
output
tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes

lo is the loopback interface, which is where this local server receives connections. Use the interface that carries your traffic, and replace 18443 with the server port. In another terminal, make one new request. --cacert ca.crt tells curl to trust the CA that issued the test server certificate, while --resolve sends localhost to 127.0.0.1. --noproxy '*' keeps the TLS session on that server when https_proxy is set, instead of opening it to a proxy:

bash
SSLKEYLOGFILE=https.keys curl -sS --noproxy '*' --tlsv1.2 --tls-max 1.3 \
  --cacert ca.crt --resolve localhost:18443:127.0.0.1 \
  https://localhost:18443/ -o /dev/null -w 'http=%{http_code}\n'
output
http=200

http=200 means the request finished. Press Ctrl+C in the tcpdump terminal. For Chrome or Firefox, export SSLKEYLOGFILE in the shell, quit any browser that is already open, and start the browser from that shell before you open the site.

Print only the labels. The rest of each line is the secret:

bash
cut -d' ' -f1 https.keys | sort -u
output
CLIENT_HANDSHAKE_TRAFFIC_SECRET
CLIENT_TRAFFIC_SECRET_0
EXPORTER_SECRET
SERVER_HANDSHAKE_TRAFFIC_SECRET
SERVER_TRAFFIC_SECRET_0

Those labels mean the file contains TLS 1.3 secrets. They do not by themselves prove the secrets belong to https.pcap. Loading the file in Wireshark is the check.


Decrypt HTTPS in Wireshark

Open https.pcap before you load the key log. The handshake is visible only through ServerHello. Everything after that is Application Data, which is the encrypted HTTP.

Wireshark packet list of an HTTPS capture before decryption, frame 6 selected, Server Hello followed by Application Data

Frame 6 is the server flight. The Info column lists Application Data several times because TLS 1.3 uses outer record type 23 for the encrypted handshake and for the HTTP that follows.

Configure the key log file

  1. Choose Edit, then Preferences. On macOS the menu is Wireshark, then Preferences.
  2. Expand Protocols and select TLS.
  3. Next to (Pre)-Master-Secret log filename, browse to https.keys and click OK.

Wireshark decodes the file again. To return to the encrypted view, clear that field and click OK. The same setting on the command line:

bash
tshark -r https.pcap -o tls.keylog_file:https.keys \
  -Y 'http.request.method' -T fields -e _ws.col.Info
output
GET / HTTP/1.1

Verify decrypted HTTP

Filter the packet list with http. Frame 9 is the request that was hidden in Application Data.

Wireshark packet list of the same HTTPS capture after the key log is loaded, frame 9 selected, showing GET / HTTP/1.1

GET / HTTP/1.1 means the session secrets matched this connection and Wireshark decoded the request. The curl status for that same request was 200. If your client negotiated HTTP/2, the filter is http2 and the Info column names HTTP/2 frames instead of GET. That is still a successful decrypt. Reading status lines and bodies is covered in analyze HTTP responses in Wireshark.


Capture LDAPS and a matching key log

Use the same order on the LDAP port. Start the capture first:

bash
sudo tcpdump -i lo -U -w ldaps.pcap port 636
output
tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes

Then run the search from a client that can write the key log. LDAPTLS_REQCERT=never is only for this test server, whose certificate is not in the default trust store. For a server you trust, leave verification on and point ldapsearch at that CA:

bash
LDAPTLS_REQCERT=never SSLKEYLOGFILE=ldaps.keys \
  ldapsearch -H ldaps://127.0.0.1:636 -x -b 'dc=example,dc=com' '(objectClass=*)' cn
output
# search result
search: 2
result: 0 Success

# numResponses: 1

result: 0 Success means the bind and the search completed. numResponses: 1 is the final search-result message. Press Ctrl+C in the tcpdump terminal. ldaps.keys from this run has one SERVER_HANDSHAKE_TRAFFIC_SECRET line, because ldapsearch opened one TLS session.

An ldapsearch linked to OpenSSL older than 3.4 can succeed at the search and still leave ldaps.keys empty. Check the library before you treat an empty file as a Wireshark bug.


Decrypt LDAPS in Wireshark

Open ldaps.pcap with no key log. It looks like the HTTPS capture: ClientHello, ServerHello, then Application Data.

Wireshark packet list of an LDAPS capture before decryption, with Application Data where the LDAP messages are still encrypted

Load ldaps.keys in the same TLS preference. Filter with ldap.

bash
tshark -r ldaps.pcap -o tls.keylog_file:ldaps.keys \
  -Y ldap -T fields -e frame.number -e _ws.col.Info
output
10	bindRequest(1) "<ROOT>" simple
16	bindResponse(1) success
18	searchRequest(2) "dc=example,dc=com" wholeSubtree
19	searchResDone(2) success  [0 results]
20	unbindRequest(3)

Verify BindRequest and SearchRequest

The Info column is the decrypted LDAP. Frame 10 is an anonymous simple bind. Frame 16 is success. Frame 18 is the search for dc=example,dc=com, and frame 19 is the result with zero entries.

Wireshark packet list filtered to ldap after decryption, with searchRequest for dc=example,dc=com selected

If those lines appear, the key log belongs to this LDAPS connection. Analyze LDAP traffic in Wireshark covers the bind and search fields once they are visible.


Why TLS decryption does not work

Empty key log

The file stays at 0 bytes, or it is never created. Typical causes:

  • The client was already running when you set SSLKEYLOGFILE. Quit it and start it again from the environment that has the variable.
  • The TLS library does not log keys. OpenSSL before 3.4 does not, unless that application added a key-log callback.
  • The variable points at a directory you cannot write, or the process is a service that never inherited your shell environment.
  • The packets came from a different program, such as a browser you did not restart, or from another device.

Wrong session

The key log has SERVER_HANDSHAKE_TRAFFIC_SECRET lines, and Wireshark still shows Application Data. A key log from another connection contains the same labels. Load the file that was written during the capture you have open, not a leftover from an earlier test.

Missing handshake

The capture started after the handshake. The key log can be correct and still fail to decrypt, because Wireshark needs the client random from ClientHello to select the secrets. Start the capture, then open one new connection. A browser tab that was already connected does not repeat the handshake.

Unsupported TLS library

curl -V or ldd on the client shows a library that does not implement SSLKEYLOGFILE. Switch to a client that does, such as this curl build, Firefox, Chromium, or openssl s_client -keylogfile, and capture that connection.

Private RSA key limitations

Loading the server’s RSA private key under the TLS preference decrypts only old TLS sessions that used RSA key transport. TLS 1.3 does not use that method, and neither do modern TLS 1.2 cipher suites that use Diffie-Hellman. Those sessions need the key log.


Embed TLS secrets into a PCAPNG file

Wireshark can store the secrets inside a pcapng file as a Decryption Secrets Block. editcap writes a new file. It does not modify https.pcap:

bash
editcap --inject-secrets tls,https.keys https.pcap https-secrets.pcapng

Open the pcapng with no key-log preference. The HTTP request is already decoded:

bash
tshark -r https-secrets.pcapng -Y 'http.request.method' -T fields -e _ws.col.Info
output
GET / HTTP/1.1

Wireshark packet list of a pcapng file with TLS secrets embedded, frame 9 selected, showing GET / HTTP/1.1 without a separate key log file

Treat that pcapng as sensitive. Anyone who opens it can decrypt the sessions whose secrets were injected. Share https.pcap when you need to hand over packets without the keys.


Can I decrypt an old PCAP without session keys?

Not for a modern TLS session. If the capture used TLS 1.3, or TLS 1.2 with Diffie-Hellman, the session keys existed only on the client and the server during that handshake. They are not in the PCAP. Finding the server’s RSA private key later does not recreate them.

You can decrypt that file only if some participant logged the secrets at the time, or if you still have a key log from that exact session. A new connection to the same server produces different secrets and does not open the old capture.


Summary

HTTPS and LDAPS in these captures were ordinary TLS 1.3. Wireshark showed Application Data until the client’s key log was loaded. After that, the HTTPS capture contained GET / HTTP/1.1, and the LDAPS capture contained bindRequest, bindResponse, and searchRequest.

SSLKEYLOGFILE produced those files here because curl and ldapsearch use OpenSSL 3.5.5, which writes the log from the library. The same variable does nothing for an older OpenSSL build that never registered key logging, or for a process that started before the variable was set. An empty file is that failure, not a sign that Wireshark cannot read TLS 1.3.

The secrets have to come from the same handshake that is in the PCAP. A capture that missed ClientHello, or a key log from another session, stays encrypted. An RSA server private key does not replace the key log. If you inject the secrets into a pcapng file, protect that file the way you would protect the key log itself.


References


Frequently Asked Questions

Why is my SSLKEYLOGFILE empty?

The client connected without a TLS stack that writes the file, or the process never saw the variable. Quit the program, set SSLKEYLOGFILE, start it again, and connect once. On OpenSSL older than 3.4 the variable is not enough unless that application turns key logging on.

Can I decrypt a PCAP I already have if I did not log keys?

Not when the session used TLS 1.3 or Diffie-Hellman. The session secrets are not in the capture, and a later connection to the same server creates different secrets.

Does the server RSA private key decrypt TLS 1.3?

No. TLS 1.3 does not encrypt the session secret to the server RSA key. Wireshark needs a key log from that session.

What does an empty search result mean after LDAPS decrypts?

searchResDone with success and zero results means the server answered the search and found no entries. The TLS decrypt worked. The directory did not contain a matching object.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)