What the TLS Handshake Looks Like on the Wire (Wireshark Lab)

Cryptography for IoT Hackers - Part 5


Series Index

Part Title Status
1 What TLS Actually Does (And Why Your IoT Device Needs It) :white_check_mark: Published
2 Certificates From Scratch The Trust Chain Nobody Explains :white_check_mark: Published
3 Setting Up Your Own CA and Issuing Certs with OpenSSL :white_check_mark: Published
4 ESP32 + TLS: Your First Secure MQTT Connection :white_check_mark: Published
5 - You are here What the TLS Handshake Looks Like on the Wire (Wireshark Lab) :white_check_mark: Published
6 Mutual TLS (mTLS) Explained: When the Server Also Verifies the Client :white_check_mark: Published
7 MITM Attack on a TLS IoT Device :soon_arrow:
8 Cert Pinning The Fix, and How Attackers Bypass It :soon_arrow:
9 Embedded Crypto Pitfalls: Hardcoded Keys and Weak RNG :soon_arrow:
10 Breaking mTLS: Stolen Certs and Certificate Confusion :soon_arrow:
11 Secure Provisioning How to Get Certs Onto Devices Safely :soon_arrow:
12 Building a Hardened ESP32 TLS Client Checklist and Final Lab :soon_arrow:
13 Secure OTA Updates Why Your Update Channel Is an Attack Surface :soon_arrow:

Before You Read This

This is Part 5. You need to have completed Parts 1 through 4 before reading this.

In Part 4 we got the ESP32 talking to a Mosquitto broker over TLS on port 8883. The messages arrived encrypted. We could see them in mosquitto_sub but only because we were the subscriber with the right setup.

In this part we are going to open Wireshark, sit on the wire like an attacker, and see exactly what is visible and what is not. And more importantly we are going to watch the TLS handshake happen live, packet by packet.

I am going to be honest when I first opened Wireshark for this, I had no idea what I was looking at. A wall of packets, random bytes, weird labels. This blog is me documenting exactly what I learned, question by question, confusion by confusion. If you are a beginner, you are in the right place.


What Are We Doing in This Part

We are going to:

  1. Set up Wireshark on Kali and capture traffic on the right interface
  2. Watch the TLS handshake happen live between ESP32 and the Mosquitto broker
  3. Look at the actual encrypted data on the wire and understand why it is unreadable
  4. Understand what cipher was selected and what each part of that cipher name means
  5. Understand how the session key is generated without ever being sent on the wire

What You Need

  • Everything from Part 4 still working ESP32 publishing to Mosquitto over TLS
  • Kali Linux with Wireshark installed
  • Same network setup Kali on Bridged Adapter, ESP32 on WiFi, same subnet

Step 1 - Check Your Interface and Verify Wireshark is Installed

Before you open Wireshark, figure out which network interface to capture on.

Run this on Kali:

ifconfig

You are looking for the interface that has your Kali IP the one we set up in Part 4. In my case it was eth0 with IP 192.168.28.67. Yours might be different but it will be eth0 in most VirtualBox setups with Bridged Adapter.

Verify Wireshark is installed:

wireshark --version

You should see something like Wireshark 4.x.x. If you get command not found, install it:

sudo apt install wireshark -y

During installation it will ask if non-root users should be able to capture packets. Select Yes.


Step 2 - Run Wireshark with sudo

This is something that tripped me up. If you just run wireshark as a normal user, you might not see any local network interfaces in the list just a blank screen or only a loopback interface.

Run it with sudo:

sudo wireshark

Now you will see all interfaces including eth0. Select eth0 and you are ready.


Step 3 - Set Your Filter

Before you start capturing, set a filter so you only see the traffic you care about. Wireshark captures everything on the network without a filter you will be drowning in irrelevant packets.

In the filter bar at the top, type:

tcp.port == 8883

This shows only packets on port 8883 which is our TLS MQTT port. Everything else is hidden.


Step 4 - Capture the Full Handshake (Important Read This First)

Here is something I got wrong the first time. I started Wireshark after the ESP32 was already connected. So my capture started from the middle I missed the Client Hello and Server Hello entirely.

The TLS handshake only happens once at the moment of connection. If you start capturing after the ESP32 is already connected, you will only see Application Data packets and wonder where the handshake went.

The correct order:

  1. Start Wireshark capture first
  2. Then reset/reboot the ESP32
  3. Watch the handshake appear from the beginning

After rebooting the ESP32, you should see something like this at the top of your capture:

204  2.799  192.168.28.66  192.168.28.67  TCP      SYN
205  2.799  192.168.28.67  192.168.28.66  TCP      SYN, ACK
206  2.808  192.168.28.66  192.168.28.67  TCP      ACK
207  2.811  192.168.28.66  192.168.28.67  TLSv1.2  Client Hello
208  2.811  192.168.28.67  192.168.28.66  TCP      ACK
209  2.812  192.168.28.67  192.168.28.66  TLSv1.2  Server Hello, Certificate, Server Key Exchange, Server Hello Done
210  2.817  192.168.28.66  192.168.28.67  TCP      ACK
228  3.314  192.168.28.66  192.168.28.67  TLSv1.2  Client Key Exchange
229  3.315  192.168.28.66  192.168.28.67  TLSv1.2  Change Cipher Spec
231  3.321  192.168.28.66  192.168.28.67  TLSv1.2  Encrypted Handshake Message
232  3.322  192.168.28.67  192.168.28.66  TLSv1.2  New Session Ticket, Change Cipher Spec, Encrypted Handshake Message
233  3.331  192.168.28.66  192.168.28.67  TLSv1.2  Application Data

That is the full TLS handshake right there, live on your screen.


What Each Packet Means - Plain Language

Let me walk through what is actually happening in each of those packets.

Packets 204, 205, 206 - TCP Handshake (SYN, SYN-ACK, ACK)

This is just TCP doing its thing before TLS even starts. The ESP32 and broker are establishing a basic network connection first. Think of it like picking up the phone and waiting for the other side to pick up. No TLS yet just "are you there?

Packet 207 - Client Hello

This is the ESP32 speaking first. It is saying:

“Hello. I am TLS 1.2 capable. Here is a list of all the encryption methods I support. You pick one. Also here is a random number I generated.”

If you click on this packet and expand it in Wireshark, you will see a long list called Cipher Suites. That is the ESP32’s full menu of encryption options it is offering to the broker.

Notice something important at this point, nothing is encrypted yet. The Client Hello is in plaintext. An attacker can see it. But that is fine there is nothing secret in it yet.

Packet 209 - Server Hello + Certificate + Server Key Exchange + Server Hello Done

This is the broker responding with four things packed into one packet:

Server Hello - the broker picked one cipher from the ESP32’s list and is announcing which one. More on this in a minute.

Certificate - this is your server.crt from Part 3 traveling on the wire. The broker is sending its identity card to the ESP32. The ESP32 will check: was this certificate signed by my Root CA? If yes I trust this broker.

Server Key Exchange - this is the broker sending its public part of the ECDHE key exchange. We will explain ECDHE properly below.

Server Hello Done - broker saying “I am done with my part of the hello, your turn.”

Packet 228 - Client Key Exchange

The ESP32 sends its public part of the ECDHE key exchange back to the broker.

At this point both sides have what they need to independently calculate the same session key. Nobody sent the session key. Both sides just did math and arrived at the same answer.

Packet 229 - Change Cipher Spec

ESP32 saying: “From this point on, everything I send will be encrypted.”

Packet 232 - Change Cipher Spec (from broker)

Broker saying the same thing back: “From this point on, everything I send will be encrypted.”

Packet 233 onwards - Application Data

Everything from here is encrypted. This is your actual MQTT messages “Hello from ESP32 TLS” but locked with AES-256. Wireshark can see the packets exist but cannot read what is inside.


What Does the Encrypted Data Look Like

If you click on any Application Data packet and look at the raw bytes at the bottom of Wireshark, you will see something like this:

0000   08 00 27 63 b0 05 88 57 21 b2 95 68 08 00 45 00
0010   00 5e 00 0a 00 00 40 06 c0 ba c0 a8 1c 42 c0 a8
0020   1c 43 f1 67 22 b3 9f 15 25 17 09 c8 8d a1 50 18
0030   14 54 e3 8c 00 00 17 03 03 00 31 00 00 00 00 00
0040   00 00 01 ec 3f 4d 2f a0 77 6b 6c c8 28 b5 61 b7
0050   c4 a6 10 66 7b c0 1f 92 7c 8d f5 ee db 8e 2f fe
0060   48 c5 ee a9 d2 3d 77 52 8c d5 67 73

That is “Hello from ESP32 TLS” completely unreadable. Random gibberish. There is no way to find your message text in there without the session key.

This is what encryption looks like on the wire. This is what you wanted when you set up TLS in Part 4. You proved it is working.


Which Cipher Was Selected

Click on packet 209 (Server Hello) in Wireshark and expand the TLS section. Look for Cipher Suite.

You will see:

Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)

That long name is actually telling you four things at once. Let me break it down:

TLS _ ECDHE _ RSA _ WITH _ AES_256_GCM _ SHA384
        |       |              |              |
        |       |              |              |
   How the    How the      The actual      How data
   session    broker       encryption      integrity
   key was    proved       algorithm       is verified
   generated  identity
  • ECDHE - how the session key was generated (math trick, never sent on wire explained below)
  • RSA - how the broker proved it is the real broker (using the RSA certificate we created in Part 3)
  • AES_256_GCM - the actual encryption. This is what turned your message into that gibberish bytes
  • SHA384 - a hashing function that makes sure nobody tampered with the data in transit

Why Can Wireshark See the Packet but Not the Message

This was my question when I first saw the Application Data packets. The packet is clearly there. Wireshark can count the bytes. But the content is unreadable.

The reason is simple. Wireshark is just a packet sniffer it captures whatever travels on the wire. It has no idea what the session key is. The session key was never on the wire.

Think of it like watching a sealed envelope pass by. You can see an envelope exists. You can see it is going from address A to address B. You can even see how thick it is. But you cannot read what is inside without opening it and you do not have the key.

Wireshark can see the envelope. It cannot open it.


The Certificate is Visible - But That is Fine

Something that confused me if you expand packet 209 in Wireshark, you can actually see the certificate in there. Wireshark even shows “Certificates (2076 bytes)”.

Does that mean the certificate is leaking sensitive data?

No. The certificate is supposed to be public. That is the whole point of it. The certificate is the broker’s identity card it is designed to be shown to anyone who connects. The ESP32 reads it, the attacker can read it, Wireshark can read it. None of that is a problem.

What the attacker CANNOT get from the certificate is the private key (server.key). That never leaves the broker. And without the private key, they cannot impersonate the broker even if they have the certificate.

What the attacker also CANNOT get is the session key. That was generated using ECDHE and never traveled on the wire.

So:

  • Certificate visible on wire = fine, it is public
  • Session key visible on wire = impossible, it was never there
  • MQTT message content visible = impossible, locked with AES-256

How the Session Key Was Generated Without Being Sent ECDHE Explained

This was the part that confused me the most. I kept thinking if both sides need the same key to encrypt and decrypt, how did they agree on it without sending it?

The answer is ECDHE. And here is the simplest way I can explain it.

Imagine you and your friend want to agree on a secret colour. But you are in a room full of people and you cannot whisper everyone can hear everything you say.

Here is what you do:

  1. You both publicly agree on a starting colour say yellow. Everyone in the room knows yellow. That is fine.
  2. You secretly pick red. You mix yellow + red and show the result orange to the room. Everyone can see orange. But nobody knows your secret red.
  3. Your friend secretly picks blue. They mix yellow + blue and show the result green to the room. Everyone can see green. But nobody knows their secret blue.
  4. Now you take their green and mix it with your secret red. You get brown.
  5. Your friend takes your orange and mixes it with their secret blue. They also get brown.

You both have brown now. That is your session key. And the entire room only ever saw yellow, orange, and green. Nobody can figure out brown from just those three.

That is ECDHE. The “colours” are big numbers and elliptic curve math instead of paint but the idea is exactly the same.

The session key was never sent. Both sides independently calculated the same value. An attacker who recorded every single packet in Wireshark still cannot get the session key from what they captured.


The Big Picture - What an Attacker Sees vs What You See

Let me put it all together. Here is what is visible and what is not:

What Visible to attacker on wire? Why
That a connection exists Yes TCP packets are visible
Which IP connected to which IP Yes IP headers are plaintext
Which port (8883) Yes Port numbers are plaintext
The broker’s certificate Yes Certificates are public by design
Which cipher was selected Yes Server Hello is plaintext
The ECDHE public values Yes These are meant to be shared
The session key No Never sent on wire
The MQTT message content No Encrypted with AES-256
The MQTT topic name No Encrypted with AES-256

An attacker on your WiFi can see that your ESP32 at 192.168.28.66 is talking to something at 192.168.28.67 on port 8883 using TLS 1.2 with AES-256-GCM. That is it. They cannot read a single MQTT message.

That is exactly what TLS is supposed to do. And you just proved it is working.


What You Should See - Screenshot Areas


Common Issues

Wireshark shows no interfaces

Run with sudo:

sudo wireshark

Without sudo, Wireshark cannot access the raw network interfaces on Kali.

I only see Application Data, no Client Hello or Server Hello

You started capturing after the ESP32 already connected. Stop the capture, reboot the ESP32 first, then start a fresh capture. The handshake only happens at connection time you have to catch it at the start.

I see too many packets, hard to find the TLS ones

Make sure your filter is set to tcp.port == 8883 before starting the capture. This filters out everything except your MQTT traffic.

My IP addresses are different from yours

That is normal. Use whatever IPs show up in your ifconfig output. The important thing is that one IP is your Kali machine and the other is your ESP32.


Quick Knowledge Check

Three questions. Answer these before moving to Part 6.

1. An attacker captures all your port 8883 traffic in Wireshark. What can they see and what can they not see?

They can see connection metadata who is talking to who, on what port, using what cipher. They cannot see the actual MQTT messages. Those are encrypted with AES-256 using a session key that was never transmitted.

2. The session key was never sent on the wire. So how do both sides have it?

ECDHE. Both sides exchange public values and independently do math to arrive at the same key. The private inputs never leave each side. The session key is calculated, not transmitted.

3. The certificate is visible in Wireshark is that a security problem?

No. The certificate is a public document. It is designed to be shared. The private key is what is sensitive and that never leaves the broker.


What is Coming in Part 6

In Part 6 we are going to set up mTLS mutual TLS. Right now only the broker proves its identity to the ESP32. In mTLS, the broker also checks the ESP32’s identity. The ESP32 needs its own certificate.

This is important in real IoT deployments you do not want just any device connecting to your broker. You want only your specific ESP32 units to be able to connect.