As the Internet of Things (IoT) expands, sending telemetry data from embedded devices to the cloud has become trivial. However, academic research in cybersecurity highlights a massive flaw in amateur IoT design: data is often transmitted in plaintext.
Whether you are building an industrial monitor or a localized escrow system, transmitting unencrypted data exposes the system to Man-in-the-Middle (MitM) attacks. In this article, we will examine the theory of cryptographic handshakes and apply it by implementing secure MQTT over TLS using C++ on an ESP32.
The Academic Theory: Asymmetric Cryptography and TLS
When an ESP32 connects to an internet server, how can it be sure the server isn't an imposter? Research in network architecture relies on Public Key Infrastructure (PKI) and Transport Layer Security (TLS).
TLS utilizes two forms of cryptography:
Asymmetric Cryptography: The server presents a "Certificate" verified by a trusted Certificate Authority (CA). The ESP32 uses a public key to verify this certificate, ensuring the server is authentic.
Symmetric Cryptography: Once identity is proven, the two devices mathematically agree on a temporary, secret "session key." All subsequent data is encrypted incredibly fast using this shared key.
The Engineering Application: WiFiClientSecure on the ESP32
In the past, performing the math required for an RSA or Elliptic Curve (ECC) handshake was impossible on an 8-bit Arduino. However, the ESP32 possesses hardware cryptographic accelerators specifically designed to handle TLS/SSL protocols natively.
Instead of using the standard WiFiClient library, developers must implement WiFiClientSecure to encrypt MQTT or HTTP payloads.
The C++ Code
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h> // Standard MQTT library
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
const char* mqtt_server = "secure-broker.example.com";
// The Root CA Certificate of your MQTT broker (in PEM format)
// This is required to mathematically verify the server's identity
const char* rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n" \
"... (Your specific CA cert data goes here) ... \n" \
"-----END CERTIFICATE-----\n";
WiFiClientSecure secureClient;
PubSubClient mqttClient(secureClient);
void setup() {
Serial.begin(115200);
// 1. Connect to standard Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi Connected");
// 2. Load the Root CA Certificate into the secure client
// This step strictly enforces the TLS handshake and prevents MitM attacks
secureClient.setCACert(rootCACertificate);
// 3. Connect to the Secure MQTT Broker (typically port 8883 for encrypted MQTT)
mqttClient.setServer(mqtt_server, 8883);
while (!mqttClient.connected()) {
Serial.println("Attempting Secure MQTT connection...");
// Connect with a client ID, username, and password
if (mqttClient.connect("ESP32_Secure_Node", "user", "pass")) {
Serial.println("Secure Connection Established!");
mqttClient.publish("telemetry/secure_data", "{\"status\": \"encrypted payload\"}");
} else {
Serial.print("Failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void loop() {
mqttClient.loop();
// Example: Publish encrypted data every 10 seconds
static unsigned long lastMsg = 0;
unsigned long now = millis();
if (now - lastMsg > 10000) {
lastMsg = now;
mqttClient.publish("telemetry/secure_data", "{\"sensor\": 42}");
}
}
Practical Considerations
While WiFiClientSecure protects data in transit, developers must be aware of the "handshake overhead." Establishing a TLS connection requires significant CPU time and memory (often taking 1-2 seconds on an ESP32). If you are designing a battery-powered device that wakes up, sends a message, and goes back to sleep, this handshake overhead will dramatically drain the battery compared to unencrypted transmission.
by Malik Hassan
.jpg)