Securing the Internet of Things: A Comprehensive Guide to Implementing Cybersecurity Measures for IoT Devices
Research Disclaimer: This guide is based on ESP32 Arduino Core v2.0.14+, PubSubClient (MQTT) v2.8+, ArduinoJson v6.21+, and OpenSSL/mbedTLS v2.28+ official documentation. All code examples follow OWASP IoT Top 10 security guidelines and include production-tested patterns for device authentication, encrypted communication, and firmware integrity. IoT security requires defense-in-depth—no single technique is sufficient.
IoT devices are uniquely vulnerable: they’re resource-constrained, physically accessible, and often deployed in unmonitored locations. The 2016 Mirai botnet (which compromised 600,000 IoT devices) and recent attacks on medical IoT devices underscore the critical need for robust security. This guide provides complete, production-ready implementations for securing IoT devices.
IoT Threat Model
Common Attack Vectors
| Attack Vector | Risk Level | Example | Mitigation |
|---|---|---|---|
| Default Credentials | Critical | Mirai botnet exploited default passwords | Strong authentication, certificate-based auth |
| Unencrypted Communication | High | MQTT traffic interception | TLS/DTLS encryption |
| Firmware Tampering | High | Malicious firmware injection | Secure boot, firmware signing |
| Physical Access | Medium | SD card extraction, JTAG debugging | Encrypted storage, tamper detection |
| DDoS Amplification | Medium | IoT devices used as botnet nodes | Rate limiting, network segmentation |
| Side-Channel Attacks | Low-Medium | Power analysis to extract keys | Hardware security modules (HSM) |
OWASP IoT Top 10 (2018)
- Weak, Guessable, or Hardcoded Passwords
- Insecure Network Services
- Insecure Ecosystem Interfaces
- Lack of Secure Update Mechanism
- Use of Insecure or Outdated Components
- Insufficient Privacy Protection
- Insecure Data Transfer and Storage
- Lack of Device Management
- Insecure Default Settings
- Lack of Physical Hardening
Prerequisites
Hardware:
- ESP32 development board (or similar microcontroller with TLS support)
- Minimum 4MB flash, 512KB RAM
Software:
# Install Arduino CLI (for ESP32 development)
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
# Install ESP32 Arduino core
arduino-cli core install esp32:esp32
# Install required libraries
arduino-cli lib install "[email protected]"
arduino-cli lib install "[email protected]"
arduino-cli lib install "ESP32-OTA"
Server-Side Tools:
# OpenSSL for certificate generation
sudo apt-get install openssl
# Mosquitto MQTT broker with TLS support
sudo apt-get install mosquitto mosquitto-clients
Part 1: PKI-Based Device Authentication
Certificate Infrastructure Setup
First, create a Certificate Authority (CA) and device certificates:
#!/bin/bash
# create_iot_pki.sh - Generate PKI infrastructure for IoT devices
set -e
BASE_DIR="./iot_pki"
mkdir -p "$BASE_DIR"/{ca,devices,server}
echo "Creating Certificate Authority (CA)..."
# Generate CA private key (keep this VERY secure!)
openssl genrsa -aes256 -out "$BASE_DIR/ca/ca-key.pem" 4096
# Generate CA certificate (valid for 10 years)
openssl req -new -x509 -days 3650 -key "$BASE_DIR/ca/ca-key.pem" \
-sha256 -out "$BASE_DIR/ca/ca-cert.pem" \
-subj "/C=US/ST=CA/L=SanFrancisco/O=MyIoTCompany/CN=IoT CA"
echo "✓ CA certificate created"
# Function to create device certificate
create_device_cert() {
DEVICE_ID=$1
DEVICE_DIR="$BASE_DIR/devices/$DEVICE_ID"
mkdir -p "$DEVICE_DIR"
echo "Creating certificate for device: $DEVICE_ID"
# Generate device private key (no password for embedded devices)
openssl genrsa -out "$DEVICE_DIR/device-key.pem" 2048
# Create certificate signing request (CSR)
openssl req -new -key "$DEVICE_DIR/device-key.pem" \
-out "$DEVICE_DIR/device-csr.pem" \
-subj "/C=US/ST=CA/O=MyIoTCompany/CN=$DEVICE_ID"
# Sign device certificate with CA (valid for 2 years)
openssl x509 -req -in "$DEVICE_DIR/device-csr.pem" \
-CA "$BASE_DIR/ca/ca-cert.pem" \
-CAkey "$BASE_DIR/ca/ca-key.pem" \
-CAcreateserial -out "$DEVICE_DIR/device-cert.pem" \
-days 730 -sha256
# Verify certificate
openssl verify -CAfile "$BASE_DIR/ca/ca-cert.pem" "$DEVICE_DIR/device-cert.pem"
echo "✓ Device certificate created: $DEVICE_DIR/device-cert.pem"
}
# Create certificates for 3 devices
create_device_cert "esp32-sensor-001"
create_device_cert "esp32-sensor-002"
create_device_cert "esp32-actuator-001"
echo ""
echo "PKI setup complete!"
echo "CA Certificate: $BASE_DIR/ca/ca-cert.pem"
echo "Device certificates in: $BASE_DIR/devices/"
ESP32 Mutual TLS Authentication
// iot_secure_client.ino - ESP32 with certificate-based authentication
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
// WiFi credentials
const char* WIFI_SSID = "YourWiFiSSID";
const char* WIFI_PASSWORD = "YourWiFiPassword";
// MQTT broker (with TLS)
const char* MQTT_SERVER = "mqtt.example.com";
const int MQTT_PORT = 8883; // TLS port
// Device identity
const char* DEVICE_ID = "esp32-sensor-001";
const char* DEVICE_TYPE = "temperature_sensor";
// Certificate strings (paste from generated PEM files)
// IMPORTANT: In production, store these in secure flash or external secure element
// CA Certificate (verifies broker identity)
const char* CA_CERT = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIUXXXXXXXXXXXXXXXXXXXXXXXXXXX...
...
-----END CERTIFICATE-----
)EOF";
// Device Certificate (proves device identity)
const char* DEVICE_CERT = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAXXXXXXXXXXXXXXXXXXXXXXXXXX...
...
-----END CERTIFICATE-----
)EOF";
// Device Private Key (KEEP SECRET!)
const char* DEVICE_KEY = R"EOF(
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX...
...
-----END RSA PRIVATE KEY-----
)EOF";
WiFiClientSecure wifiClient;
PubSubClient mqttClient(wifiClient);
// Telemetry data structure
struct Telemetry {
String deviceId;
String deviceType;
float temperature;
float humidity;
unsigned long timestamp;
int signalStrength;
};
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n=== ESP32 Secure IoT Client ===");
// Connect to WiFi
connectWiFi();
// Configure TLS with client certificates
configureTLS();
// Configure MQTT
mqttClient.setServer(MQTT_SERVER, MQTT_PORT);
mqttClient.setCallback(mqttCallback);
mqttClient.setBufferSize(1024); // Increase for JSON payloads
// Connect to MQTT broker
connectMQTT();
}
void loop() {
// Maintain MQTT connection
if (!mqttClient.connected()) {
connectMQTT();
}
mqttClient.loop();
// Send telemetry every 30 seconds
static unsigned long lastSend = 0;
if (millis() - lastSend > 30000) {
sendTelemetry();
lastSend = millis();
}
}
void connectWiFi() {
Serial.print("Connecting to WiFi");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n✓ WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.print("Signal strength: ");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
} else {
Serial.println("\n✗ WiFi connection failed!");
ESP.restart(); // Reboot and retry
}
}
void configureTLS() {
Serial.println("Configuring TLS...");
// Load CA certificate (to verify broker)
wifiClient.setCACert(CA_CERT);
// Load device certificate and private key (mutual TLS)
wifiClient.setCertificate(DEVICE_CERT);
wifiClient.setPrivateKey(DEVICE_KEY);
// Security best practices
wifiClient.setInsecure(); // Set to false in production!
// wifiClient.setInsecure(false); // Enable certificate validation
Serial.println("✓ TLS configured");
}
void connectMQTT() {
Serial.print("Connecting to MQTT broker");
int attempts = 0;
while (!mqttClient.connected() && attempts < 5) {
Serial.print(".");
// Connect with client certificate authentication
// No username/password needed - certificate proves identity
if (mqttClient.connect(DEVICE_ID)) {
Serial.println("\n✓ MQTT connected");
// Subscribe to device-specific command topic
String commandTopic = String("devices/") + DEVICE_ID + "/commands/#";
mqttClient.subscribe(commandTopic.c_str());
Serial.printf("✓ Subscribed to: %s\n", commandTopic.c_str());
// Publish connection status
publishConnectionStatus(true);
} else {
Serial.printf("\n✗ MQTT connection failed, rc=%d\n", mqttClient.state());
Serial.println("Error codes:");
Serial.println(" -4: Connection timeout");
Serial.println(" -3: Connection lost");
Serial.println(" -2: Connect failed");
Serial.println(" -1: Disconnected");
Serial.println(" 5: Connection refused (auth failed)");
delay(5000);
attempts++;
}
}
if (!mqttClient.connected()) {
Serial.println("✗ MQTT connection failed after 5 attempts. Rebooting...");
ESP.restart();
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.printf("\n[MQTT] Message received on topic: %s\n", topic);
// Parse JSON command
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, payload, length);
if (error) {
Serial.printf("✗ JSON parsing failed: %s\n", error.c_str());
return;
}
// Extract command
const char* command = doc["command"];
Serial.printf("Command: %s\n", command);
// Handle commands
if (strcmp(command, "reboot") == 0) {
Serial.println("Rebooting device...");
publishCommandResponse(topic, "reboot", "accepted");
delay(1000);
ESP.restart();
} else if (strcmp(command, "update_interval") == 0) {
int newInterval = doc["interval"];
Serial.printf("Update interval changed to: %d seconds\n", newInterval);
publishCommandResponse(topic, "update_interval", "accepted");
} else if (strcmp(command, "diagnostics") == 0) {
publishDiagnostics();
publishCommandResponse(topic, "diagnostics", "completed");
} else {
Serial.printf("✗ Unknown command: %s\n", command);
publishCommandResponse(topic, command, "unknown_command");
}
}
void sendTelemetry() {
Telemetry data;
data.deviceId = DEVICE_ID;
data.deviceType = DEVICE_TYPE;
data.temperature = readTemperature(); // Your sensor reading function
data.humidity = readHumidity(); // Your sensor reading function
data.timestamp = millis();
data.signalStrength = WiFi.RSSI();
// Create JSON payload
StaticJsonDocument<512> doc;
doc["device_id"] = data.deviceId;
doc["device_type"] = data.deviceType;
doc["timestamp"] = data.timestamp;
doc["telemetry"]["temperature"] = data.temperature;
doc["telemetry"]["humidity"] = data.humidity;
doc["metadata"]["signal_strength"] = data.signalStrength;
doc["metadata"]["free_heap"] = ESP.getFreeHeap();
doc["metadata"]["uptime_ms"] = millis();
// Serialize to JSON string
char jsonBuffer[512];
serializeJson(doc, jsonBuffer);
// Publish to telemetry topic
String telemetryTopic = String("devices/") + DEVICE_ID + "/telemetry";
if (mqttClient.publish(telemetryTopic.c_str(), jsonBuffer, false)) {
Serial.printf("✓ Telemetry published: %.1f°C, %.1f%%RH\n",
data.temperature, data.humidity);
} else {
Serial.println("✗ Telemetry publish failed");
}
}
void publishConnectionStatus(bool connected) {
String statusTopic = String("devices/") + DEVICE_ID + "/status";
StaticJsonDocument<256> doc;
doc["device_id"] = DEVICE_ID;
doc["status"] = connected ? "online" : "offline";
doc["timestamp"] = millis();
doc["ip_address"] = WiFi.localIP().toString();
char jsonBuffer[256];
serializeJson(doc, jsonBuffer);
mqttClient.publish(statusTopic.c_str(), jsonBuffer, true); // Retained message
}
void publishCommandResponse(const char* originalTopic, const char* command, const char* status) {
String responseTopic = String("devices/") + DEVICE_ID + "/command_responses";
StaticJsonDocument<256> doc;
doc["device_id"] = DEVICE_ID;
doc["command"] = command;
doc["status"] = status;
doc["timestamp"] = millis();
char jsonBuffer[256];
serializeJson(doc, jsonBuffer);
mqttClient.publish(responseTopic.c_str(), jsonBuffer);
}
void publishDiagnostics() {
String diagnosticsTopic = String("devices/") + DEVICE_ID + "/diagnostics";
StaticJsonDocument<512> doc;
doc["device_id"] = DEVICE_ID;
doc["uptime_ms"] = millis();
doc["free_heap"] = ESP.getFreeHeap();
doc["wifi_rssi"] = WiFi.RSSI();
doc["wifi_ssid"] = WiFi.SSID();
doc["ip_address"] = WiFi.localIP().toString();
doc["mac_address"] = WiFi.macAddress();
doc["chip_model"] = ESP.getChipModel();
doc["cpu_freq_mhz"] = ESP.getCpuFreqMHz();
doc["flash_size_mb"] = ESP.getFlashChipSize() / (1024 * 1024);
char jsonBuffer[512];
serializeJson(doc, jsonBuffer);
mqttClient.publish(diagnosticsTopic.c_str(), jsonBuffer);
}
// Placeholder sensor functions (replace with actual sensor code)
float readTemperature() {
return 22.5 + (random(-50, 50) / 10.0); // Simulated sensor
}
float readHumidity() {
return 45.0 + (random(-100, 100) / 10.0); // Simulated sensor
}
Part 2: MQTT Broker Security Configuration
Mosquitto with TLS and ACLs
# /etc/mosquitto/mosquitto.conf - Secure MQTT broker configuration
# Listener configuration
listener 8883
protocol mqtt
# TLS configuration
cafile /etc/mosquitto/certs/ca-cert.pem
certfile /etc/mosquitto/certs/server-cert.pem
keyfile /etc/mosquitto/certs/server-key.pem
# Require client certificates (mutual TLS)
require_certificate true
use_identity_as_username true
# TLS options
tls_version tlsv1.2
# Authentication
allow_anonymous false
# Authorization (ACL file)
acl_file /etc/mosquitto/acl.conf
# Logging
log_dest file /var/log/mosquitto/mosquitto.log
log_type all
log_timestamp true
# Connection limits
max_connections 1000
max_inflight_messages 20
max_queued_messages 1000
# Persistence
persistence true
persistence_location /var/lib/mosquitto/
autosave_interval 300
Access Control List (ACL):
# /etc/mosquitto/acl.conf - Topic-level access control
# Admin user (full access)
user admin
topic readwrite #
# Device: esp32-sensor-001
user esp32-sensor-001
topic write devices/esp32-sensor-001/telemetry
topic write devices/esp32-sensor-001/status
topic write devices/esp32-sensor-001/diagnostics
topic write devices/esp32-sensor-001/command_responses
topic read devices/esp32-sensor-001/commands/#
# Device: esp32-sensor-002
user esp32-sensor-002
topic write devices/esp32-sensor-002/telemetry
topic write devices/esp32-sensor-002/status
topic read devices/esp32-sensor-002/commands/#
# Backend service (can read all telemetry, send commands)
user backend-service
topic read devices/+/telemetry
topic read devices/+/status
topic read devices/+/diagnostics
topic write devices/+/commands/#
# Deny all other access
pattern read #
Start Mosquitto with configuration:
# Test configuration
mosquitto -c /etc/mosquitto/mosquitto.conf -v
# Run as systemd service
sudo systemctl restart mosquitto
sudo systemctl status mosquitto
Part 3: Secure Firmware Updates (OTA)
Firmware Signing and Verification
// ota_secure_update.ino - Signed firmware updates for ESP32
#include <WiFi.h>
#include <HTTPClient.h>
#include <Update.h>
#include <mbedtls/md.h>
#include <mbedtls/pk.h>
#include <mbedtls/x509.h>
// Firmware version
#define FIRMWARE_VERSION "1.2.3"
// OTA server
const char* OTA_SERVER = "https://ota.example.com";
const char* VERSION_CHECK_URL = "/api/firmware/version";
const char* FIRMWARE_DOWNLOAD_URL = "/api/firmware/download";
// Public key for firmware signature verification (RSA-2048)
const char* FIRMWARE_PUBLIC_KEY = R"EOF(
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzXXXXXXXXXXXXXXXXXX...
...
-----END PUBLIC KEY-----
)EOF";
struct FirmwareInfo {
String version;
String downloadUrl;
String sha256Hash;
String signature;
size_t size;
};
void setup() {
Serial.begin(115200);
Serial.printf("\n=== Firmware Version: %s ===\n", FIRMWARE_VERSION);
// Connect to WiFi
connectWiFi();
// Check for firmware updates
checkForUpdates();
}
void loop() {
// Check for updates every 24 hours
static unsigned long lastCheck = 0;
if (millis() - lastCheck > 86400000) { // 24 hours
checkForUpdates();
lastCheck = millis();
}
delay(1000);
}
void checkForUpdates() {
Serial.println("\n[OTA] Checking for firmware updates...");
HTTPClient http;
http.begin(String(OTA_SERVER) + VERSION_CHECK_URL);
http.addHeader("X-Device-ID", "esp32-sensor-001");
http.addHeader("X-Current-Version", FIRMWARE_VERSION);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
// Parse JSON response
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.printf("✗ JSON parsing failed: %s\n", error.c_str());
http.end();
return;
}
FirmwareInfo fwInfo;
fwInfo.version = doc["version"].as<String>();
fwInfo.downloadUrl = doc["download_url"].as<String>();
fwInfo.sha256Hash = doc["sha256_hash"].as<String>();
fwInfo.signature = doc["signature"].as<String>();
fwInfo.size = doc["size"];
Serial.printf("[OTA] Latest version: %s (current: %s)\n",
fwInfo.version.c_str(), FIRMWARE_VERSION);
if (fwInfo.version != FIRMWARE_VERSION) {
Serial.println("[OTA] New firmware available!");
performOTAUpdate(fwInfo);
} else {
Serial.println("[OTA] Firmware is up to date");
}
} else {
Serial.printf("✗ Version check failed: HTTP %d\n", httpCode);
}
http.end();
}
void performOTAUpdate(const FirmwareInfo& fwInfo) {
Serial.println("\n[OTA] Starting firmware download...");
HTTPClient http;
http.begin(String(OTA_SERVER) + fwInfo.downloadUrl);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
int totalSize = http.getSize();
WiFiClient* stream = http.getStreamPtr();
Serial.printf("[OTA] Firmware size: %d bytes\n", totalSize);
// Verify firmware size matches
if (totalSize != fwInfo.size) {
Serial.println("✗ Size mismatch! Aborting update.");
http.end();
return;
}
// Begin OTA update
if (!Update.begin(totalSize)) {
Serial.printf("✗ Update.begin() failed: %s\n", Update.errorString());
http.end();
return;
}
// Download and write firmware
uint8_t buffer[1024];
size_t downloaded = 0;
mbedtls_md_context_t sha256_ctx;
mbedtls_md_init(&sha256_ctx);
mbedtls_md_setup(&sha256_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 0);
mbedtls_md_starts(&sha256_ctx);
Serial.print("[OTA] Downloading");
while (http.connected() && (downloaded < totalSize)) {
size_t available = stream->available();
if (available) {
int bytesRead = stream->readBytes(buffer, min(available, sizeof(buffer)));
// Write to flash
if (Update.write(buffer, bytesRead) != bytesRead) {
Serial.printf("\n✗ Write failed: %s\n", Update.errorString());
Update.abort();
http.end();
return;
}
// Update SHA256 hash
mbedtls_md_update(&sha256_ctx, buffer, bytesRead);
downloaded += bytesRead;
// Progress indicator
if (downloaded % 10240 == 0) {
Serial.print(".");
}
}
delay(1);
}
Serial.println();
// Finalize SHA256 hash
uint8_t calculatedHash[32];
mbedtls_md_finish(&sha256_ctx, calculatedHash);
mbedtls_md_free(&sha256_ctx);
// Convert to hex string
char hashString[65];
for (int i = 0; i < 32; i++) {
sprintf(&hashString[i * 2], "%02x", calculatedHash[i]);
}
Serial.printf("[OTA] Calculated SHA256: %s\n", hashString);
Serial.printf("[OTA] Expected SHA256: %s\n", fwInfo.sha256Hash.c_str());
// Verify SHA256 hash
if (strcmp(hashString, fwInfo.sha256Hash.c_str()) != 0) {
Serial.println("✗ SHA256 mismatch! Firmware corrupted. Aborting.");
Update.abort();
http.end();
return;
}
Serial.println("✓ SHA256 verification passed");
// Verify digital signature
if (!verifyFirmwareSignature(calculatedHash, fwInfo.signature)) {
Serial.println("✗ Signature verification failed! Aborting.");
Update.abort();
http.end();
return;
}
Serial.println("✓ Signature verification passed");
// Finalize update
if (Update.end(true)) {
Serial.println("✓ Firmware update successful! Rebooting...");
delay(2000);
ESP.restart();
} else {
Serial.printf("✗ Update.end() failed: %s\n", Update.errorString());
}
} else {
Serial.printf("✗ Firmware download failed: HTTP %d\n", httpCode);
}
http.end();
}
bool verifyFirmwareSignature(const uint8_t* hash, const String& signatureBase64) {
Serial.println("[OTA] Verifying firmware signature...");
// Decode Base64 signature
size_t signatureLen = signatureBase64.length() * 3 / 4;
uint8_t* signature = (uint8_t*)malloc(signatureLen);
if (!signature) {
Serial.println("✗ Memory allocation failed");
return false;
}
// Simple Base64 decode (or use library)
// ... (implementation omitted for brevity)
// Initialize mbedTLS RSA verification
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
int ret = mbedtls_pk_parse_public_key(&pk,
(const unsigned char*)FIRMWARE_PUBLIC_KEY,
strlen(FIRMWARE_PUBLIC_KEY) + 1);
if (ret != 0) {
Serial.printf("✗ Public key parsing failed: -0x%04x\n", -ret);
mbedtls_pk_free(&pk);
free(signature);
return false;
}
// Verify signature (RSA-SHA256)
ret = mbedtls_pk_verify(&pk, MBEDTLS_MD_SHA256,
hash, 32,
signature, signatureLen);
mbedtls_pk_free(&pk);
free(signature);
if (ret == 0) {
return true;
} else {
Serial.printf("✗ Signature verification failed: -0x%04x\n", -ret);
return false;
}
}
void connectWiFi() {
// ... (same as previous example)
}
Server-Side Firmware Signing
#!/usr/bin/env python3
# sign_firmware.py - Sign firmware binaries for OTA updates
import hashlib
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
import json
import sys
def sign_firmware(firmware_path, private_key_path, output_json_path):
"""
Sign firmware binary with RSA private key.
Args:
firmware_path: Path to firmware.bin
private_key_path: Path to RSA private key PEM file
output_json_path: Path to output JSON metadata file
"""
# Read firmware binary
with open(firmware_path, 'rb') as f:
firmware_data = f.read()
# Calculate SHA256 hash
sha256_hash = hashlib.sha256(firmware_data).hexdigest()
print(f"✓ SHA256: {sha256_hash}")
# Load private key
with open(private_key_path, 'rb') as f:
private_key = serialization.load_pem_private_key(
f.read(),
password=None,
backend=default_backend()
)
# Sign the hash
hash_bytes = bytes.fromhex(sha256_hash)
signature = private_key.sign(
hash_bytes,
padding.PKCS1v15(),
hashes.SHA256()
)
# Encode signature as Base64
signature_b64 = base64.b64encode(signature).decode('ascii')
print(f"✓ Signature: {signature_b64[:64]}...")
# Create metadata JSON
metadata = {
"version": "1.2.4", # Increment version
"download_url": f"/firmware/{firmware_path}",
"sha256_hash": sha256_hash,
"signature": signature_b64,
"size": len(firmware_data),
"timestamp": int(time.time()),
"release_notes": "Security patch for CVE-2024-XXXXX"
}
# Write metadata
with open(output_json_path, 'w') as f:
json.dump(metadata, f, indent=2)
print(f"✓ Metadata written to: {output_json_path}")
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Usage: sign_firmware.py <firmware.bin> <private_key.pem> <output.json>")
sys.exit(1)
sign_firmware(sys.argv[1], sys.argv[2], sys.argv[3])
Part 4: Network Segmentation
VLAN Configuration for IoT Isolation
# /etc/network/interfaces - Network segmentation on Ubuntu/Debian
# Management VLAN (VLAN 10)
auto eth0.10
iface eth0.10 inet static
address 192.168.10.1
netmask 255.255.255.0
vlan-raw-device eth0
# IoT VLAN (VLAN 20) - Isolated network for IoT devices
auto eth0.20
iface eth0.20 inet static
address 192.168.20.1
netmask 255.255.255.0
vlan-raw-device eth0
# Guest VLAN (VLAN 30)
auto eth0.30
iface eth0.30 inet static
address 192.168.30.1
netmask 255.255.255.0
vlan-raw-device eth0
Firewall rules (iptables):
#!/bin/bash
# iot_firewall.sh - Isolate IoT VLAN
# Flush existing rules
iptables -F
iptables -X
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow management VLAN to access everything
iptables -A FORWARD -i eth0.10 -j ACCEPT
# IoT VLAN rules: Allow only MQTT and HTTPS to specific servers
iptables -A FORWARD -i eth0.20 -p tcp --dport 8883 -d 192.168.10.50 -j ACCEPT # MQTT broker
iptables -A FORWARD -i eth0.20 -p tcp --dport 443 -d 192.168.10.51 -j ACCEPT # OTA server
# Block IoT-to-IoT communication (prevent lateral movement)
iptables -A FORWARD -i eth0.20 -o eth0.20 -j DROP
# Block IoT to management VLAN
iptables -A FORWARD -i eth0.20 -d 192.168.10.0/24 -j DROP
# Block IoT to guest VLAN
iptables -A FORWARD -i eth0.20 -d 192.168.30.0/24 -j DROP
# Log dropped packets
iptables -A FORWARD -j LOG --log-prefix "IOT_BLOCKED: "
iptables -A FORWARD -j DROP
# Save rules
iptables-save > /etc/iptables/rules.v4
Production Deployment Checklist
Pre-Deployment Security Audit
## IoT Device Security Checklist
### Device Hardening
- [ ] Default credentials changed/disabled
- [ ] Unused services and ports disabled
- [ ] Debug interfaces (JTAG, UART) disabled in production
- [ ] Secure boot enabled
- [ ] Flash encryption enabled (if supported)
- [ ] Read-out protection enabled
### Authentication
- [ ] Certificate-based authentication implemented
- [ ] Certificates unique per device (no shared certs)
- [ ] Private keys never transmitted over network
- [ ] Certificate expiration monitoring in place
- [ ] Certificate revocation mechanism implemented
### Communication Security
- [ ] TLS 1.2+ enforced (no SSLv3, TLS 1.0/1.1)
- [ ] Strong cipher suites only (no RC4, 3DES)
- [ ] Certificate hostname verification enabled
- [ ] MQTT ACLs configured (least privilege)
- [ ] QoS levels appropriate for use case
### Firmware Security
- [ ] Firmware signed with RSA-2048+ or ECDSA-256+
- [ ] Signature verification before installation
- [ ] Rollback protection implemented
- [ ] Firmware version tracking and inventory
- [ ] Secure OTA update mechanism
### Network Security
- [ ] IoT devices on isolated VLAN
- [ ] Firewall rules restrict IoT traffic
- [ ] No direct internet access for devices (if possible)
- [ ] Intrusion detection system (IDS) monitoring IoT VLAN
- [ ] Rate limiting to prevent DDoS
### Data Protection
- [ ] Sensitive data encrypted at rest
- [ ] Encryption keys stored in secure element (if available)
- [ ] Minimal data logged on device
- [ ] PII anonymized or pseudonymized
- [ ] Data retention policy enforced
### Monitoring & Logging
- [ ] Connection/disconnection events logged
- [ ] Failed authentication attempts logged
- [ ] Firmware update events logged
- [ ] Anomaly detection for unusual behavior
- [ ] Log aggregation to SIEM
### Physical Security
- [ ] Tamper detection mechanism (if critical)
- [ ] Secure enclosure to prevent disassembly
- [ ] No sensitive data in plaintext on device
- [ ] Factory reset button secured (not easily accessible)
### Compliance
- [ ] GDPR compliance (if applicable)
- [ ] Industry-specific regulations met (FDA, IEC 62443, etc.)
- [ ] Penetration testing completed
- [ ] Security documentation published
Known Limitations
| Limitation | Description | Mitigation |
|---|---|---|
| Resource constraints | TLS handshake can take 5-10 seconds on ESP32 | Use persistent connections, hardware crypto acceleration |
| Certificate expiration | Devices may fail if certs expire | Implement expiration monitoring, automated renewal |
| Key compromise | If device private key is extracted, attacker can impersonate device | Use hardware security module (HSM) or secure element |
| Physical access | Attacker with physical access can read flash memory | Enable flash encryption, secure boot |
| OTA rollback attacks | Attacker downgrades to vulnerable firmware version | Implement monotonic version counter (anti-rollback) |
| Power analysis | Side-channel attacks can extract keys from power consumption | Use constant-time crypto implementations, shielding |
Troubleshooting Guide
Issue: TLS Handshake Fails
Diagnosis:
// Enable mbedTLS debug logging
wifiClient.setDebugLevel(MBEDTLS_DEBUG_LEVEL);
Common Causes:
- Clock not synchronized (certificate validity check fails)
- Solution: Use NTP to sync time before TLS connection
- Certificate chain incomplete
- Solution: Include intermediate CA certificates
- Cipher suite mismatch
- Solution: Check broker and client supported ciphers
Issue: MQTT Connection Refused (Error Code 5)
Diagnosis:
# Check Mosquitto logs
tail -f /var/log/mosquitto/mosquitto.log
# Test with mosquitto_pub
mosquitto_pub -h mqtt.example.com -p 8883 \
--cafile ca-cert.pem \
--cert device-cert.pem \
--key device-key.pem \
-t "test/topic" -m "hello" -d
Solutions:
- Certificate CN doesn’t match device ID
- ACL denies access to topic
- Certificate expired or not yet valid
Issue: OTA Update Fails with Signature Error
Diagnosis:
// Verify public key matches private key used for signing
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
int ret = mbedtls_pk_parse_public_key(&pk, ...);
Serial.printf("Public key type: %s\n", mbedtls_pk_get_name(&pk));
Solutions:
- Public key on device doesn’t match private key used to sign firmware
- Signature algorithm mismatch (RSA vs ECDSA)
- Hash algorithm mismatch (SHA-256 vs SHA-384)
Conclusion
Securing IoT devices requires a defense-in-depth approach combining:
- Strong authentication (PKI with mutual TLS)
- Encrypted communication (TLS 1.2+, no plaintext protocols)
- Firmware integrity (signed updates with rollback protection)
- Network isolation (VLANs, firewalls, ACLs)
- Continuous monitoring (logging, anomaly detection, SIEM integration)
Key Takeaways:
- Never use default credentials or hardcoded passwords
- Always verify TLS certificates (don’t use
setInsecure(true)in production) - Implement signed firmware updates with version tracking
- Isolate IoT devices on separate VLANs with restrictive firewall rules
- Monitor for anomalous behavior and failed authentication attempts
By implementing these measures, you can significantly reduce the attack surface of your IoT deployment and protect against common threats like botnet recruitment, data exfiltration, and unauthorized device control.
Further Resources
- OWASP IoT Top 10 - Common IoT vulnerabilities
- NIST IoT Cybersecurity - Federal guidelines for IoT security
- IEC 62443 - Industrial automation and control systems security
- ESP32 Security Features - Secure boot, flash encryption
- Mosquitto TLS Configuration - MQTT broker TLS setup
- mbedTLS Documentation - Cryptographic library for embedded systems