MQTT-Sicherheit

Ende-zu-Ende-Verschlüsselung von MQTT-Payloads mit AES-GCM

TLS schützt MQTT-Nachrichten bei der Übertragung, aber der Broker sieht weiterhin Klartext. So ergänzen Sie Ende-zu-Ende-Verschlüsselung von MQTT-Payloads mit AES-GCM in Python und gehen sicher mit Schlüsseln, Nonces und Topics um.

Illustration zur MQTT-Payload-Verschlüsselung mit einem versiegelten Umschlag, der von einem Sensor über einen Broker zu einer Anwendung gelangt
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

MQTT-Payload-Verschlüsselung bedeutet, den Nachrichteninhalt selbst zu verschlüsseln, sodass nur der Publisher und die vorgesehenen Subscriber ihn lesen können, unabhängig davon, wer den Broker betreibt. In diesem Leitfaden erfahren Sie, wann sich Ende-zu-Ende-Verschlüsselung zusätzlich zu TLS lohnt, wie Sie das Nachrichtenformat sicher gestalten und wie Sie das Ganze in Python mit AES-GCM über eine echte Broker-Verbindung umsetzen.

TLS vs. MQTT-Payload-Verschlüsselung

TLS verschlüsselt die Verbindung zwischen jedem Client und dem Broker. Das ist unverzichtbar, und Sie sollten es immer nutzen; siehe unseren Leitfaden zur MQTT-TLS-Konfiguration. TLS endet aber am Broker. Im Broker liegt eine Nachricht im Klartext vor. Das muss so sein, damit der Broker sie routen, als Retained Message speichern oder für eine Offline-Sitzung einreihen kann.

Payload-Verschlüsselung fügt eine zweite Schicht hinzu, die mit der Nachricht vom Publisher bis zum Subscriber reist.

Eigenschaft Nur TLS TLS + Payload-Verschlüsselung
Im Netzwerk geschützt Ja Ja
Für den Broker-Betreiber lesbar Ja Nein
In gespeicherten Retained- oder Queued-Nachrichten lesbar Ja Nein
Topic-Namen verborgen Nein (für den Broker sichtbar) Nein (weiterhin für den Broker sichtbar)
Broker kann Payloads prüfen oder dekodieren Ja Nein
Schlüsselverwaltung nötig Nur Broker-Zertifikat Anwendungsschlüssel auf jedem Client

Beachten Sie die beiden „Nein“-Zeilen, die oft übersehen werden. Topics bleiben lesbar, weil der Broker sie für das Routing braucht; legen Sie also niemals Geheimnisse in Topic-Namen ab. Und jede Broker-Funktion, die Payloads liest, funktioniert bei verschlüsselten Nachrichten nicht mehr. So können beispielsweise der Live-Topic-Explorer, die Schwellenwert-Alarmregeln und die Sparkplug-B-Dekodierung von ConnectMQ nur Daten anzeigen oder auswerten, die sie parsen können; verschlüsselte Payloads erscheinen dort als undurchsichtige Bytes.

Wann Ende-zu-Ende-Verschlüsselung sinnvoll ist

Ergänzen Sie Payload-Verschlüsselung, wenn:

  • Vorschriften oder Verträge verlangen, dass Infrastrukturanbieter die Daten nicht lesen können.
  • Nachrichten über geteilte oder öffentliche Broker, Bridges oder Gateways laufen, die Sie nicht vollständig kontrollieren.
  • Retained Messages oder Queued Sessions sensible Werte enthalten, etwa personenbezogene Daten im Sinne der DSGVO oder Zugangscodes.
  • Payloads Betriebsgeheimnisse transportieren, zum Beispiel Rezepturen, Prozessparameter oder Maschineneinstellungen, die das Know-how eines Maschinenbauers oder Fertigers ausmachen.
  • Sie Defense in Depth wollen, falls Zugangsdaten zu einem Teil des Systems geleakt werden.

Verzichten Sie darauf, wenn Sie Einblick auf Broker-Seite brauchen (Dashboards im Broker-Portal, Alarmregeln auf Werte) und dem Betreiber vertrauen, oder wenn ressourcenarme Geräte sich den zusätzlichen Code und die Schlüsselverwaltung nicht leisten können. Viele Teams verschlüsseln nur einige sensible Topics, etwa .../commands/unlock, und belassen Telemetrie im Klartext über TLS.

Das gilt besonders für einen Unified Namespace (UNS) nach ISA-95: Sein Zweck ist, dass MES, SCADA, Historian und Analysen die Maschinendaten unter einer gemeinsamen Struktur wie acme/werk-stuttgart/montage/linie-1/zelle-3/... direkt lesen können. Verschlüsseln Sie den gesamten UNS, muss jeder Konsument Schlüssel verwalten, und der Nutzen schwindet. Bewährt hat sich, nur einzelne Teilbäume wie Rezepturen oder Befehle zu verschlüsseln.

Einen Algorithmus wählen

Verwenden Sie einen Algorithmus für authentifizierte Verschlüsselung. Er verschlüsselt den Payload und fügt ein Tag hinzu, das jede Veränderung erkennt. Zwei gute Optionen:

  • AES-256-GCM: weit verbreitet, auf vielen CPUs und auf dem ESP32 (über mbedTLS) hardwarebeschleunigt. Erfordert pro Nachricht unter demselben Schlüssel eine eindeutige 96-Bit-Nonce.
  • XChaCha20-Poly1305 (libsodium crypto_aead_xchacha20poly1305_ietf): schnell in Software, und seine 192-Bit-Nonce kann ohne realistisches Kollisionsrisiko zufällig erzeugt werden.

Verwenden Sie kein AES-CBC ohne MAC, keinerlei ECB-Modus und keine „Verschlüsselung“ mit XOR oder Base64. Sie vermitteln ein falsches Gefühl von Sicherheit.

Das verschlüsselte Nachrichtenformat entwerfen

Ein einfaches, robustes Format für AES-GCM ist:

| version (1 byte) | nonce (12 bytes) | ciphertext + 16-byte tag |

Drei Designregeln sind wichtiger als der Algorithmus:

  1. Verwenden Sie eine Nonce nie zweimal mit demselben Schlüssel. Bei AES-GCM kann eine Wiederverwendung Klartext offenlegen und Fälschungen ermöglichen. Eine zufällige 12-Byte-Nonce ist für moderate Nachrichtenmengen pro Schlüssel akzeptabel; rotieren Sie bei Publishern mit hohem Volumen regelmäßig die Schlüssel.
  2. Binden Sie den Ciphertext an sein Topic. Übergeben Sie das Topic als Additional Authenticated Data (AAD). Andernfalls könnte ein Angreifer mit Broker-Zugriff eine verschlüsselte „unlock“-Nachricht auf dem Topic eines anderen Geräts wiederholen.
  3. Fügen Sie einen Zeitstempel oder Zähler in den Klartext ein, damit Subscriber alte, später wiederholte Nachrichten ablehnen können.

Python-Beispiel: verschlüsseltes Publish und Subscribe

Das folgende Beispiel nutzt paho-mqtt 2.x und das Paket cryptography. Installieren Sie beides zuerst:

pip install "paho-mqtt>=2.0" cryptography

Erzeugen Sie einmalig einen Schlüssel und teilen Sie ihn über Ihren Secret Store oder die Umgebung mit Publisher und Subscriber, nicht über MQTT:

python -c "import os,base64;print(base64.b64encode(os.urandom(32)).decode())"

Speichern Sie Folgendes als secure_mqtt.py:

import base64
import json
import os
import sys
import time

import paho.mqtt.client as mqtt
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.exceptions import InvalidTag

BROKER = "broker.connectmq.com"
PORT = 8883
TOPIC = "connectmq/demo/e2e-91c4/door/command"
VERSION = b"\x01"
MAX_AGE_SECONDS = 30

KEY = base64.b64decode(os.environ["MQTT_PAYLOAD_KEY"])
aead = AESGCM(KEY)


def encrypt(topic: str, data: dict) -> bytes:
    data["ts"] = int(time.time())
    plaintext = json.dumps(data).encode()
    nonce = os.urandom(12)
    ciphertext = aead.encrypt(nonce, plaintext, topic.encode())
    return VERSION + nonce + ciphertext


def decrypt(topic: str, blob: bytes) -> dict:
    if len(blob) < 1 + 12 + 16 or blob[:1] != VERSION:
        raise ValueError("unknown message format")
    nonce, ciphertext = blob[1:13], blob[13:]
    plaintext = aead.decrypt(nonce, ciphertext, topic.encode())
    data = json.loads(plaintext)
    if abs(time.time() - data["ts"]) > MAX_AGE_SECONDS:
        raise ValueError("stale or replayed message")
    return data


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    if userdata == "sub":
        client.subscribe(TOPIC, qos=1)


def on_message(client, userdata, msg):
    try:
        print("Decrypted:", decrypt(msg.topic, msg.payload))
    except (InvalidTag, ValueError) as exc:
        print("Rejected message:", exc)


def make_client(role: str) -> mqtt.Client:
    client = mqtt.Client(
        mqtt.CallbackAPIVersion.VERSION2,
        client_id=f"e2e-{role}-91c4",
        userdata=role,
    )
    client.tls_set()  # TLS schützt weiterhin Zugangsdaten und Metadaten
    # Privater ConnectMQ-Broker:
    # client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
    client.on_connect = on_connect
    client.on_message = on_message
    client.connect(BROKER, PORT, keepalive=60)
    return client


if __name__ == "__main__":
    role = sys.argv[1] if len(sys.argv) > 1 else "sub"
    client = make_client(role)
    if role == "pub":
        client.loop_start()
        time.sleep(1)
        payload = encrypt(TOPIC, {"action": "unlock", "door": "lab-2"})
        client.publish(TOPIC, payload, qos=1).wait_for_publish()
        print("Published", len(payload), "encrypted bytes")
        client.loop_stop()
        client.disconnect()
    else:
        client.loop_forever()

Starten Sie Subscriber und Publisher in zwei Terminals mit demselben Schlüssel:

export MQTT_PAYLOAD_KEY="paste-the-base64-key-here"
python secure_mqtt.py sub      # Terminal 1
python secure_mqtt.py pub      # Terminal 2

Wenn Sie dasselbe Topic mit mosquitto_sub abonnieren, sehen Sie nur binäres Rauschen, und genau darum geht es. Ändern Sie ein Byte eines Payloads, publizieren Sie ihn auf ein anderes Topic oder warten Sie vor dem Entschlüsseln länger als 30 Sekunden, und der Subscriber lehnt ihn ab. Da das Beispiel den öffentlichen MQTT-Broker nutzt, der für Tests und Prototypen gedacht ist, könnte jeder den Ciphertext sehen, aber ohne den Schlüssel kann ihn niemand lesen oder fälschen.

Schlüsselverwaltung: der schwierige Teil

Der Verschlüsselungscode ist kurz; an der Schlüsselverwaltung entscheidet sich, ob Projekte gelingen oder scheitern.

  1. Begrenzen Sie den Geltungsbereich von Schlüsseln. Verwenden Sie einen Schlüssel pro Gerät oder Gerätegruppe, nicht einen für die gesamte Flotte. Ein geleakter Schlüssel sollte so wenig wie möglich preisgeben.
  2. Speichern Sie Schlüssel sicher auf Geräten. Nutzen Sie auf Mikrocontrollern verschlüsselten Flash oder ein Secure Element und auf Servern einen Keystore des Betriebssystems oder einen Secret Manager. Unser Leitfaden zum Speichern von MQTT-Zugangsdaten auf Geräten gilt gleichermaßen für Verschlüsselungsschlüssel.
  3. Fügen Sie eine Schlüssel-ID hinzu. Erweitern Sie den Header um eine Schlüsselkennung, damit Subscriber den richtigen Schlüssel wählen und Sie ohne Ausfallzeit rotieren können.
  4. Rotieren Sie Schlüssel. Führen Sie einen neuen Schlüssel ein, lassen Sie Subscriber beide akzeptieren, stellen Sie die Publisher um und ziehen Sie dann den alten Schlüssel zurück.
  5. Erwägen Sie asymmetrische Verfahren für Many-to-One-Flüsse. Mit Sealed Boxes von libsodium können Tausende Geräte an den öffentlichen Schlüssel eines Backends verschlüsseln, während nur das Backend den privaten Schlüssel besitzt.

Embedded-Geräte

Auf einem ESP32 stellt mbedTLS (das für TLS ohnehin vorhanden ist) mbedtls_gcm_crypt_and_tag und mbedtls_gcm_auth_decrypt bereit, sodass Payload-Verschlüsselung kaum Codegröße hinzufügt. Verwenden Sie für Nonces den Hardware-Zufallszahlengenerator (esp_fill_random) und stellen Sie sicher, dass WLAN oder Bluetooth läuft oder eine geeignete Entropiequelle aktiviert ist, damit die RNG-Ausgabe wirklich zufällig ist.

Häufige Implementierungsfehler

Bei der Prüfung von Payload-Verschlüsselungscode in IoT-Projekten tauchen immer wieder dieselben Fehler auf:

  • Eine feste Nonce oder ein Zähler, der beim Neustart zurückgesetzt wird. Ein Gerät, das seinen Zähler nach jedem Stromzyklus bei null beginnt, verwendet Nonces wieder. Nutzen Sie zufällige Nonces oder speichern Sie den Zähler dauerhaft im Flash.
  • Entschlüsselungsfehler ignorieren. Ein Subscriber, der einen InvalidTag-Fehler protokolliert und die Nachricht trotzdem verarbeitet, hat überhaupt keinen Schutz. Behandeln Sie jeden Fehler als verworfene Nachricht.
  • Nach dem Verschlüsseln komprimieren. Ciphertext lässt sich nicht komprimieren; komprimieren Sie also zuerst das JSON, wenn die Größe eine Rolle spielt.
  • Formate ohne Versionsbyte mischen. Wenn Sie Algorithmen wechseln oder eine Schlüssel-ID hinzufügen, müssen alte und neue Geräte unterscheidbar sein. Genau dafür gibt es das Versionsbyte im Beispiel.
  • Den Schlüssel im selben Repository wie den Code ablegen. Schlüssel gehören in einen Secret Store, in Umgebungsvariablen oder in sicheren Speicher auf dem Gerät, niemals in Git.

Testen Sie die Negativpfade gezielt: Publizieren Sie eine abgeschnittene Nachricht, eine Nachricht mit einem gekippten Bit und eine wiederholte alte Nachricht, und bestätigen Sie, dass jede davon abgelehnt wird.

Verschlüsselung ersetzt keine Broker-Sicherheit

Payload-Verschlüsselung schützt Vertraulichkeit und Integrität der Daten, hindert aber niemanden daran, sich zu verbinden, Topics zu fluten oder Metadaten zu abonnieren. Sie brauchen weiterhin TLS, starke Zugangsdaten pro Gerät und Schutzmechanismen auf Broker-Seite. Ein verwalteter MQTT-Broker wie ConnectMQ bietet TLS, an Client IDs gebundene Zugangsdaten, Quotas pro Plan, Flood Protection und IP-Sperren nach wiederholten fehlgeschlagenen Logins, während die Ende-zu-Ende-Verschlüsselung die Daten selbst privat hält. Zusammen decken sie die Checkliste aus unseren Best Practices für MQTT-Sicherheit ab.

Starten Sie mit ConnectMQ

Erstellen Sie unter app.connectmq.com einen kostenlosen Broker im Serverless-Plan, ganz ohne Kreditkarte, und führen Sie das obige Beispiel mit Ihren eigenen Zugangsdaten auf Port 8883 aus. ConnectMQ kümmert sich um TLS, Authentifizierung und Skalierung, während Ihre Anwendung die Payload-Schlüssel behält.

Häufig gestellte Fragen

Hat MQTT eine eingebaute Payload-Verschlüsselung?

Nein. MQTT 3.1.1 und 5.0 definieren keine Payload-Verschlüsselung; Payloads sind undurchsichtige Bytes. Die Verschlüsselung erfolgt per TLS auf der Verbindung und optional durch Ihre Anwendung auf dem Payload.

Kann ich auch den Topic-Namen verschlüsseln?

Nicht, ohne das Routing zu zerstören, denn der Broker gleicht Subscriptions anhand der Topic-Namen ab. Halten Sie Topics frei von sensiblen Informationen und verwenden Sie undurchsichtige Geräte-IDs statt Namen oder Adressen.

Erhöht Payload-Verschlüsselung die Nachrichtengröße?

Geringfügig. Das obige AES-GCM-Format fügt pro Nachricht 29 Bytes hinzu (Version, Nonce und Tag), plus eventuellen Kodierungs-Overhead, wenn Sie das Ergebnis Base64-kodieren.

Sollte ich weiterhin TLS verwenden, wenn Payloads verschlüsselt sind?

Ja. TLS schützt Ihre MQTT-Zugangsdaten, Client IDs, Topic-Namen und Protokoll-Metadaten und authentifiziert den Broker; nichts davon deckt die Payload-Verschlüsselung ab.

Verwandte Artikel

MQTT-Zugangsdaten ohne Ausfallzeit rotieren

Eine Schritt-für-Schritt-Strategie mit zwei parallelen Zugangsdaten, um MQTT-Benutzernamen und -Passwörter in einer Geräteflotte zu rotieren: neue Zugangsdaten bereitstellen, mit Fallback umschalten, prüfen und erst dann die alten widerrufen.

7 Min. Lesezeit

MQTT-Zugangsdaten sicher auf Embedded-Geräten speichern

Ein praxisnaher Vergleich der Möglichkeiten, MQTT-Benutzernamen und -Passwörter auf ESP32- und Linux-Geräten zu schützen, von verschlüsseltem NVS und Flash-Verschlüsselung bis zu Secure Elements und TPM-gestützten systemd-Credentials.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.