cd ..

End-to-End Encrypted MQTT IoT Gateway

Protocol: MQTT v3.1.1 | Encryption: AES-256-CBC (application layer) | Role: Systems / IoT Engineering
Node.js MQTT.js AES-256-CBC HMAC-SHA256 Publish / Subscribe Broker-Blind E2E Mosquitto Broker

MQTT is the backbone of industrial IoT. It was designed in 1999 for satellite telemetry — low bandwidth, high latency, unreliable links — and it has aged into the dominant protocol for device-to-cloud messaging. MQTT handles reliable delivery, topic routing, QoS levels, and retained messages. What it does not handle is confidentiality of the payload.

A standard MQTT deployment routes all messages through a central broker. The broker sees every message in plaintext. If the broker is compromised, all messages are exposed. If the broker is a third-party cloud service, you are trusting that provider with your sensor data. I built a system where the broker is structurally blind to the message content — encryption happens at the publisher before the message enters the network, and decryption happens at the subscriber after it exits.

AES-256 Encryption Strength
0 Plaintext at Broker
CBC Mode (IV per message)
JSON App-Layer Payload
QoS 1 At-Least-Once Delivery
N+1 Subscriber Scaling

The Problem with Transport-Level Encryption

Standard MQTT deployments use TLS to encrypt the transport — the connection between the device and the broker. This protects against a network eavesdropper. But it offers no protection against a compromised broker, a cloud provider's employees, or an API that exposes stored messages. The encryption terminates at the broker — not at the subscriber.

TLS-Only (Standard)

Encrypted in transit between publisher → broker and broker → subscriber. Broker sees plaintext at all times. A compromised broker, retained message leak, or cloud provider access exposes all data.

E2E Encryption (This System)

Encrypted from publisher's memory to subscriber's memory. Broker only ever handles ciphertext. Broker compromise yields useless encrypted bytes. Key management is decoupled from the messaging infrastructure.

The Encryption Protocol

AES-256-CBC (Cipher Block Chaining) is the encryption mode. CBC requires an Initialization Vector (IV) — a 16-byte random value that ensures identical plaintext payloads produce different ciphertext, preventing pattern analysis. The IV is not a secret; it is transmitted with the message. The key is the secret.

Wire Format

The MQTT payload is a compact binary structure: the IV followed immediately by the ciphertext. The subscriber knows the format and splits the payload accordingly.

IV 16 bytes Random initialization vector. Unique per message. Not secret.
Ciphertext variable AES-256-CBC encrypted JSON payload. PKCS7-padded to 16-byte blocks.
// Publisher: encrypt before publishing
function encryptPayload(data, sharedKey) {
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv('aes-256-cbc', sharedKey, iv);
    const json = JSON.stringify(data);
    const encrypted = Buffer.concat([
        cipher.update(json, 'utf8'),
        cipher.final()
    ]);
    // IV prepended — subscriber splits at byte 16
    return Buffer.concat([iv, encrypted]);
}

// Subscriber: decrypt on receive
function decryptPayload(buffer, sharedKey) {
    const iv = buffer.subarray(0, 16);
    const ciphertext = buffer.subarray(16);
    const decipher = crypto.createDecipheriv('aes-256-cbc', sharedKey, iv);
    const decrypted = Buffer.concat([
        decipher.update(ciphertext),
        decipher.final()
    ]);
    return JSON.parse(decrypted.toString('utf8'));
}

System Architecture

[ Publisher (Node.js) ]
    │
    ├──► JSON.stringify(payload)
    ├──► IV = crypto.randomBytes(16)
    ├──► ciphertext = AES-256-CBC(key, IV, payload)
    ├──► wire = concat(IV + ciphertext)
    │
    ▼
[ MQTT Broker (Mosquitto) ]
    │  ← sees only: binary blob (IV + ciphertext)
    │  ← topic routing only — zero plaintext access
    │
    ▼
[ Subscriber (Node.js) ]
    │
    ├──► IV = wire[0:16]
    ├──► ciphertext = wire[16:]
    ├──► plaintext = AES-256-CBC-Decrypt(key, IV, ciphertext)
    └──► data = JSON.parse(plaintext)

Topic Design and QoS

MQTT topics are hierarchical strings — factory/line-3/sensor/temperature — that the broker uses for routing. Because the broker does routing based on topics, topics remain plaintext even in this E2E-encrypted system. This is an inherent constraint: the broker cannot route encrypted topic strings it cannot read.

Topic design principle: Topics should contain only the routing context needed by the broker — device ID, sensor type, region. They should never contain sensitive data that needs encryption. The payload carries the sensitive data; the topic carries the routing metadata. This is the same separation-of-concerns principle as an IP packet: the IP header (topic) is plaintext routing; the TCP payload is encrypted data.

QoS 1: At-Least-Once Delivery

The system uses QoS level 1. Under QoS 1, the broker acknowledges each message with a PUBACK. If the publisher does not receive a PUBACK within a timeout, it retransmits. This guarantees at-least-once delivery: messages survive broker restarts and transient network drops. Because each CBC-encrypted message uses a unique random IV, duplicate messages produce different ciphertext — duplicates are identifiable at the application layer via a message sequence number in the JSON payload.

Key Management Considerations

E2E encryption shifts the security boundary from the broker to the key. A compromised key compromises all past messages if the same key was used throughout. The system is designed with key rotation in mind:

Extending to IoT at Scale

The publish/subscribe pattern decouples publishers from subscribers in three ways: publishers don't know how many subscribers exist, subscribers don't know when publishers will publish, and neither needs the other to be online simultaneously (with retained messages). This makes the system naturally scalable:

Engineering outcome: The resulting system treats the MQTT broker as an untrusted relay — equivalent to how TLS treats the network as untrusted. The broker provides valuable services (reliability, routing, fan-out) without requiring trust in its confidentiality. This is the correct security posture for multi-tenant cloud MQTT deployments where you share broker infrastructure with other tenants.