MQTT-Zugangsdaten auf Geräten zu schützen bedeutet, sicherzustellen, dass Benutzername und Passwort, mit denen Ihr Gerät den Broker erreicht, nicht aus Firmware-Images, ausgelesenen Flash-Chips, Debug-Schnittstellen oder Logdateien gelesen werden können. Dieser Leitfaden vergleicht die praktischen Optionen auf ESP32- und Linux-Geräten, zeigt jeweils die Konfiguration und listet die Fehler auf, durch die Zugangsdaten im Feld am häufigsten durchsickern.
Das Bedrohungsmodell in einfachen Worten
Bevor Sie eine Speichermethode wählen, sollten Sie klären, gegen wen Sie sich verteidigen:
| Angreifer | Zugriff | Was er versucht |
|---|---|---|
| Jemand mit Ihrer Firmware-Datei | Eine .bin von einer Download-Seite oder aus einem Support-Ticket |
Das Image nach Strings durchsuchen |
| Jemand mit einem Gerät | Physischer Zugriff, ein USB-Seriell-Adapter | Flash über UART auslesen, JTAG anschließen |
| Malware oder ein Bug auf dem Gerät | Code, der auf dem Gerät läuft | Dateien oder Speicher lesen |
| Jemand im Netzwerk | Paketmitschnitt | Das Passwort aus CONNECT lesen |
Gegen den Angreifer im Netzwerk hilft TLS auf Port 8883 (oder 8084 für WebSockets), das jeder ConnectMQ-Broker mit einem Let's-Encrypt-Zertifikat unterstützt; MQTT-Authentifizierung mit Benutzername und Passwort erklärt, warum Zugangsdaten niemals über Port 1883 übertragen werden dürfen. Dieser Artikel konzentriert sich auf die anderen drei: den Schutz des Geheimnisses im Ruhezustand auf dem Gerät.
Regel null: Zugangsdaten gehören nicht in die Firmware
Ein #define MQTT_PASSWORD "..." im Quellcode landet im Firmware-Binary, in Ihrer Git-Historie, in CI-Logs und in jeder Kopie des Images. Ein strings firmware.bin | grep -i pass findet es in einem typischen Hobbyprojekt oft innerhalb von Sekunden. Die Firmware muss für jedes Gerät identisch sein, die Zugangsdaten müssen pro Gerät eindeutig sein. Halten Sie beides getrennt und schreiben Sie die Zugangsdaten bei der sicheren IoT-Geräte-Provisionierung in den Speicher.
Optionen auf dem ESP32
Unverschlüsseltes NVS (Basis, nicht sicher)
Die Non-Volatile-Storage-Bibliothek (oder der Arduino-Wrapper Preferences) speichert Schlüssel-Wert-Paare in einer Flash-Partition. Damit sind Zugangsdaten von der Firmware getrennt, was bereits eine Verbesserung ist, doch die Daten liegen unverschlüsselt vor. Wer den Flash-Chip auslesen kann, kann auch das Passwort lesen.
NVS-Verschlüsselung plus Flash-Verschlüsselung
ESP-IDF kann NVS-Partitionen verschlüsseln. Im klassischen Verfahren liegen die NVS-Schlüssel in einer eigenen Partition nvs_keys, die durch Flash-Verschlüsselung geschützt ist, deren Schlüssel wiederum in eFuses liegt, die Software nicht zurücklesen kann. Neuere Chips wie ESP32-S3 und ESP32-C3 unterstützen zusätzlich ein HMAC-basiertes Verfahren, das die NVS-Schlüssel aus einem eFuse-Schlüssel ableitet, ohne Partition nvs_keys.
Eine Partitionstabelle mit verschlüsselter Schlüsselpartition:
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x6000,
nvs_keys, data, nvs_keys, , 0x1000, encrypted
phy_init, data, phy, , 0x1000,
factory, app, factory, , 1M,
Relevante Optionen in sdkconfig:
CONFIG_SECURE_FLASH_ENC_ENABLED=y
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y
CONFIG_NVS_ENCRYPTION=y
CONFIG_SECURE_BOOT=y
CONFIG_SECURE_BOOT_V2_ENABLED=y
Der Release-Modus ist entscheidend: Der Development-Modus lässt Wege offen, unverschlüsselte Images neu zu flashen, was den Zweck bei ausgelieferten Geräten zunichtemacht. Secure Boot ergänzt die Verschlüsselung, indem es keine Firmware ausführt, die Sie nicht signiert haben, sodass ein Angreifer keinen Code flashen kann, der die entschlüsselten Zugangsdaten ausliest und ausgibt. Beides brennt eFuses unumkehrbar, testen Sie daher zuerst auf Ersatzplatinen und folgen Sie Schritt für Schritt der Espressif-Dokumentation zur Flash-Verschlüsselung.
Das Lesen der Zugangsdaten in einem Arduino-Sketch auf dem ESP32 sieht gleich aus, ob NVS verschlüsselt ist oder nicht, und genau darum geht es: Der Schutz liegt unterhalb der API.
#include <Preferences.h>
struct MqttCreds { String user, pass, clientId; };
bool loadCreds(MqttCreds &c) {
Preferences prefs;
if (!prefs.begin("mqtt", true)) return false; // schreibgeschützt
c.user = prefs.getString("user", "");
c.pass = prefs.getString("pass", "");
c.clientId = prefs.getString("cid", "");
prefs.end();
return c.user.length() && c.pass.length() && c.clientId.length();
}
void logCreds(const MqttCreds &c) {
// Identität protokollieren, niemals das Passwort
Serial.printf("MQTT user=%s clientId=%s pass=<%u bytes>\n",
c.user.c_str(), c.clientId.c_str(), c.pass.length());
}
Secure Elements
Ein Secure Element wie der ATECC608 von Microchip ist ein separater Chip, der Schlüssel speichert und kryptografische Operationen ausführt, ohne private Schlüssel jemals herauszugeben. Am stärksten ist er bei zertifikatsbasierter Identität, bei der der private Schlüssel für Mutual TLS den Chip nie verlässt. Bei Passwort-Authentifizierung kann ein Secure Element dennoch helfen, indem es den Schlüssel hält, mit dem der Passwort-Blob im Flash verschlüsselt ist, und so die Hürde für physisches Auslesen erhöht. Es verursacht zusätzliche Stücklistenkosten, Platinenfläche und Treiberintegration und findet sich daher vor allem in Produkten mit hochwertigen Daten oder regulatorischem Druck, etwa durch den EU Cyber Resilience Act oder die Komponentenanforderungen von IEC 62443-4-2 für Geräte in Industrieanlagen.
Optionen auf Linux-Geräten
Gateways und Edge-Computer mit Linux, etwa das IIoT-Gateway neben der SPS im Schaltschrank, bieten umfangreichere Werkzeuge.
Dateiberechtigungen (Basis)
Speichern Sie Zugangsdaten außerhalb des Anwendungsverzeichnisses in einer Datei, die nur das Dienstkonto lesen kann:
sudo install -d -m 0700 -o mqttsvc -g mqttsvc /etc/mqtt-agent
sudo install -m 0600 -o mqttsvc -g mqttsvc credentials.env /etc/mqtt-agent/credentials.env
Das schützt vor anderen Benutzern und Diensten, nicht aber vor jemandem, der die SD-Karte entnimmt. Kombinieren Sie es für physischen Schutz mit vollständiger Festplattenverschlüsselung.
Verschlüsselte systemd-Credentials mit TPM
Auf Systemen mit systemd und einem TPM-2.0-Chip verschlüsselt systemd-creds ein Geheimnis so, dass es nur auf dieser Maschine und nur für den Dienst entschlüsselt werden kann, der es deklariert:
# Passwort verschlüsseln, gebunden an das TPM dieser Maschine
echo -n 'YOUR_PASSWORD' | sudo systemd-creds encrypt --with-key=tpm2 \
--name=mqtt-password - /etc/credstore.encrypted/mqtt-password
# /etc/systemd/system/mqtt-agent.service
[Unit]
Description=MQTT telemetry agent
After=network-online.target
Wants=network-online.target
[Service]
User=mqttsvc
LoadCredentialEncrypted=mqtt-password:/etc/credstore.encrypted/mqtt-password
Environment=MQTT_USERNAME=YOUR_USERNAME MQTT_CLIENT_ID=YOUR_CLIENT_ID
ExecStart=/usr/bin/python3 /opt/mqtt-agent/agent.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Der Dienst liest das entschlüsselte Geheimnis aus einem privaten Verzeichnis im Arbeitsspeicher:
import os
import ssl
import paho.mqtt.client as mqtt
cred_dir = os.environ["CREDENTIALS_DIRECTORY"]
with open(os.path.join(cred_dir, "mqtt-password")) as f:
password = f.read().strip()
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=os.environ["MQTT_CLIENT_ID"],
protocol=mqtt.MQTTv5)
client.username_pw_set(os.environ["MQTT_USERNAME"], password)
client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
client.connect("YOUR_BROKER_HOST", 8883, keepalive=60)
client.loop_forever()
Ein kopiertes Festplatten-Image ist auf einer anderen Maschine wertlos, weil das TPM die Zugangsdaten dort nicht entsiegelt.
Speicheroptionen im Vergleich
| Methode | Plattform | Schützt vor Firmware-Leaks | Schützt vor Flash-Dump | Aufwand |
|---|---|---|---|---|
| Fest in der Firmware | Beliebig | Nein | Nein | Keiner |
| Unverschlüsseltes NVS / Konfigurationsdatei | ESP32 / Linux | Ja | Nein | Gering |
| NVS-Verschlüsselung plus Flash-Verschlüsselung, Secure Boot | ESP32 | Ja | Ja | Mittel, unumkehrbare eFuses |
| Secure Element | ESP32, Linux | Ja | Ja, am stärksten für Schlüssel | Hoch |
| Dateiberechtigungen plus Festplattenverschlüsselung | Linux | Ja | Teilweise | Gering bis mittel |
| systemd-creds mit TPM | Linux | Ja | Ja | Mittel |
Zugangsdaten im Arbeitsspeicher, in Backups und bei Updates
Verschlüsselter Speicher schützt die Zugangsdaten im Ruhezustand, doch sie müssen trotzdem in den RAM entschlüsselt werden, um das CONNECT-Paket zu bauen. Einige Gewohnheiten verringern das Risiko auf dem restlichen Weg:
- An einer Stelle halten. Laden Sie die Zugangsdaten einmal in einen einzigen Puffer, der dem MQTT-Modul gehört, statt sie in globale Strings, Konfigurationsstrukturen und Diagnoseobjekte zu kopieren.
- Aus Diagnosedaten ausschließen. Core Dumps, Absturzberichte und Remote-Log-Uploads dürfen niemals den Speicher mit den Zugangsdaten enthalten. Halten Sie unter Linux die Datei mit den Zugangsdaten aus Backup-Sätzen und Support-Bundles heraus.
- Nicht über die Cloud hin- und herschicken. Eine Funktion zur Konfigurationssynchronisation, die die Geräteeinstellungen in Ihr Backend hochlädt, sollte die Felder mit Zugangsdaten vollständig auslassen.
- Updates atomar schreiben. Wenn bei einer Rotation neue Zugangsdaten eintreffen, schreiben Sie sie in einen zweiten Slot und schalten Sie erst nach erfolgreicher Verbindung um, damit ein Stromausfall das Gerät nicht mit einem halb geschriebenen Passwort zurücklässt.
- Bei der Außerbetriebnahme löschen. Löschen Sie den NVS-Namespace oder die Datei mit den Zugangsdaten im Rahmen von Werksreset und Außerbetriebnahme.
Fehler, durch die Zugangsdaten durchsickern
- Das Passwort in Debug-Logs oder Crash Dumps ausgeben.
- Es „der Bequemlichkeit halber" auf einer Geräte-Webseite anzeigen, selbst maskiert im HTML.
- Es in einer Retained Message speichern oder an ein gemeinsam genutztes Topic senden.
- UART-Download-Modus und JTAG auf Seriengeräten aktiviert lassen.
- Dieselben Zugangsdaten für die ganze Flotte verwenden, sodass ein einziges Auslesen alles kompromittiert.
- Nie rotieren, sodass Zugangsdaten, die vor Jahren geleakt wurden, immer noch funktionieren.
Punkt 5 verdient besondere Betonung. Bei ConnectMQ ist jede Client ID an die für sie ausgestellten Zugangsdaten gebunden, geben Sie also jedem Gerät einen eigenen Satz; ein ausgelesenes Passwort legt dann nur ein Gerät offen, das Sie mit dem Verfahren aus MQTT-Zugangsdaten rotieren widerrufen und ersetzen können. Dank Mandantenisolation erreichen selbst die Zugangsdaten eines kompromittierten Geräts nur Ihren eigenen Broker, und die IP-Sperre nach wiederholten fehlgeschlagenen Logins bremst Angreifer, die andere Passwörter erraten wollen. Die weiteren Maßnahmen behandelt der Artikel Best Practices für MQTT-Sicherheit.
Starten Sie mit ConnectMQ
Kombinieren Sie geschützten Speicher auf dem Gerät mit einem verwalteten MQTT-Broker, der TLS und die Ausstellung von Zugangsdaten für Sie übernimmt. Erstellen Sie unter app.connectmq.com einen kostenlosen Serverless-Broker, ganz ohne Kreditkarte, und testen Sie die Speicherung Ihrer Zugangsdaten auf ESP32 oder Linux gegen einen echten TLS-Endpunkt. Wenn Sie neu auf der Hardware-Seite sind, ist das ESP32-MQTT-Tutorial ein guter Einstieg, und der Leitfaden zum Cloud-MQTT-Broker erklärt die Hosting-Optionen.
Häufig gestellte Fragen
Ist die ESP32-Bibliothek Preferences sicher für MQTT-Passwörter?
Nur wenn NVS-Verschlüsselung und Flash-Verschlüsselung aktiviert sind. Ohne sie speichert Preferences die Daten im Klartext im Flash, den jeder mit physischem Zugriff auslesen kann.
Brauche ich ein Secure Element für MQTT-Zugangsdaten?
Nicht immer. Verschlüsselter Speicher mit Flash-Verschlüsselung und Secure Boot reicht für viele Produkte aus; ein Secure Element ist vor allem bei zertifikatsbasierter Identität oder besonders schützenswerten Deployments wertvoll.
Wo sollte ein Linux-Gateway sein MQTT-Passwort speichern?
Mindestens in einer Datei mit Berechtigungen 0600, die dem Dienstkonto gehört. Wo ein TPM vorhanden ist, binden verschlüsselte systemd-Credentials das Geheimnis an diese Maschine.
Schützt TLS die auf dem Gerät gespeicherten Zugangsdaten?
Nein. TLS schützt Zugangsdaten bei der Übertragung. Der Schutz des Speichers ist eine separate Schicht, die Sie auf dem Gerät selbst brauchen.



