11.13. Pairing und Bonding

Alles, was bis hierher behandelt wurde, überträgt Bytes im Klartext über den Funkkanal. Jeder mit einem BLE-fähigen Laptop im selben Raum kann die Advertising-Kanäle abhören, der Hopping-Sequenz einer offenen Verbindung folgen und jeden Read-, Write- und Notification-Vorgang mitlesen, der darüber läuft. Für die meisten öffentlichen Sensordaten (Batteriestand, Umgebungstemperatur) ist das in Ordnung. Für alles, was die beiden Endpunkte privat halten möchten – ein Steuerregister, das ein Relais scharfschaltet, ein Passwort, eine Messung, die nicht breit gesendet werden soll – muss die Verbindung verschlüsselt werden, und idealerweise muss die Kamera wissen, mit wem sie spricht.

BLE bietet beides über Pairing und Bonding.

11.13.1. Pairing, Bonding, Verschlüsselung

Drei eng miteinander verwandte Konzepte:

  • Verschlüsselung ist das eigentliche Ziel. Sobald die Verbindung verschlüsselt ist, kann jedes Paket auf den Datenkanälen nur von den beiden Endpunkten entschlüsselt werden; ein Lauscher sieht nur Rauschen.

  • Pairing ist die Prozedur, die die beiden Endpunkte ausführen, um sich auf die Schlüssel zu einigen, die die Verschlüsselung verwendet. Es handelt sich um einen einmaligen Austausch, der gemeinsames Schlüsselmaterial erzeugt, das die Link-Schicht in ihre Verschlüsselungs-Engine einspeist.

  • Bonding ist die Entscheidung, die Schlüssel nach Abschluss des Pairings im nichtflüchtigen Speicher zu persistieren, sodass die nächste Verbindung zwischen denselben beiden Geräten das Pairing überspringt und direkt zur Verschlüsselung übergeht.

Im Klartext: Pairing ist „stellt euch vor“; Bonding ist „merkt euch diese Vorstellung“; Verschlüsselung ist „sprecht von nun an unter euch“.

Zwei Spalten mit den Bezeichnungen "Central" und "Peripheral". Eine gestrichelte Linie nahe dem oberen Rand mit der Bezeichnung "BLE-Verbindung offen (unverschlüsselt)". Darunter drei Pfeile: "pairing request" vom Central zum Peripheral, "key exchange" in beide Richtungen, "pairing complete" vorwärts. Eine zweite gestrichelte Linie darunter mit der Bezeichnung "link encrypted". Zwei dicke bidirektionale Pfeile tragen "encrypted GATT traffic". Eine optionale "store keys to flash"-Box an der Seite, beschriftet mit "bonding".

Der Pairing-Ablauf auf Basis einer offenen BLE-Verbindung. Sobald der Schlüsselaustausch abgeschlossen ist, verschlüsselt die Link-Schicht jedes weitere Paket. Bonding ist der zusätzliche Schritt, die Schlüssel in den Flash zu schreiben.

11.13.2. LE Secure Connections

Der moderne Schlüsselaustausch, den BLE verwendet, ist LE Secure Connections, das auf Elliptic Curve Diffie-Hellman basiert. Beide Seiten erzeugen ein temporäres Schlüsselpaar, tauschen die öffentlichen Hälften aus und kombinieren das Ergebnis mit ihren eigenen privaten Schlüsseln, um zum selben gemeinsamen Geheimnis zu gelangen – ein Geheimnis, das ein Lauscher selbst mit einer vollständigen Aufzeichnung des Austauschs nicht berechnen kann.

Die ältere Methode LE Legacy ist weniger sicher (ein Lauscher mit dem vollständigen Austausch kann den Schlüssel meist wiederherstellen) und existiert nur zur Abwärtskompatibilität mit alten Peripheriegeräten. Der Standard von aioble ist die moderne Methode (le_secure=True); behalte sie bei.

11.13.3. Pairing initiieren

Ein Central startet das Pairing, indem es aioble.DeviceConnection.pair() für eine bereits offene Verbindung aufruft:

async with await device.connect() as connection:
    await connection.pair(bond=True, le_secure=True, mitm=False)
    # ... GATT work, now over an encrypted link ...

Nachdem pair zurückkehrt, spiegeln die Attribute encrypted, authenticated, bonded und key_size der Verbindung wider, was ausgehandelt wurde.

Die vier nützlichsten Schlüsselwortargumente:

  • bond=True – speichert die resultierenden Schlüssel im Flash, sodass die nächste Verbindung zwischen denselben beiden Geräten den Pairing-Handshake überspringt. Standard True.

  • le_secure=True – verwendet LE Secure Connections. Standard True. Lass es aktiviert.

  • mitm=False – legt fest, ob Man-in-the-Middle-Schutz erforderlich ist. Dies benötigt einen Out-of-Band-Kanal (ein numerischer Code, der auf einer Seite angezeigt und auf der anderen bestätigt wird, ein eingegebener Passkey, …), damit der Benutzer überprüfen kann, dass die beiden Geräte im Pairing-Handshake tatsächlich diejenigen sind, für die er sie hält. Standardwert ist False (kein MITM-Schutz – ein passiver Lauscher kann die Verbindung nicht mitlesen, aber ein Angreifer, der Verbindungen aktiv umleitet, könnte sich selbst einkoppeln). Setze es für alles Sensible auf True, beachte aber, dass das Peripheriegerät tatsächlich eine IO-Fähigkeit unterstützen muss.

  • io=3 – die IO-Fähigkeit, die das Gerät angibt. Die Bluetooth-Spezifikation definiert fünf: 0 nur Anzeige, 1 Anzeige + Ja/Nein, 2 nur Tastatur, 3 keine Eingabe, keine Ausgabe, 4 Tastatur + Anzeige. Eine Kamera ohne UI meldet typischerweise 3; falls die Kamera selbst eine Anzeige hat, könnte die Anwendung die numerische Bestätigung anzeigen und 1 verwenden. Die Kombination der IO-Fähigkeiten beider Seiten entscheidet, ob echter MITM-Schutz erreichbar ist.

Peripheriegeräte rufen pair nicht selbst auf – sie reagieren auf das, was das Central initiiert. Ob für eine bestimmte Charakteristik Verschlüsselung erforderlich ist, ist eine Eigenschaft davon, wie sie in der GATT-Datenbank deklariert ist; die Zugriffsbits, die Verschlüsselung erfordern, sind Teil der Low-Level-bluetooth-API und werden derzeit nicht über den Charakteristik-Konstruktor von aioble bereitgestellt.

11.13.4. Bonding – und wo die Schlüssel leben

Wenn bond=True gesetzt ist, schreibt aioble die Schlüssel in eine JSON-Datei im lokalen Dateisystem. Der Standard-Dateiname ist ble_secrets.json, geschrieben relativ zum aktuellen Arbeitsverzeichnis. Auf einer frisch gebooteten Kamera hat _boot.py dieses Verzeichnis bereits gewählt: /sdcard, wenn eine Karte eingehängt ist, andernfalls /flash – die Datei landet also unter /sdcard/ble_secrets.json oder /flash/ble_secrets.json. Die Datei enthält die Einträge, die benötigt werden, um die Verbindung beim nächsten Wiederverbinden des gebondeten Peers neu zu verschlüsseln, einschließlich der Identitätsadresse des Peers.

Eine Asymmetrie, die man im Hinterkopf behalten sollte: das Speichern geschieht automatisch, sobald sich Schlüssel ändern, aber das Laden der Datei beim nächsten Boot nicht. Rufe aioble.security.load_secrets() einmal beim Start auf (vor jedem Pairing oder Advertising), damit zuvor gebondete Peers erkannt werden:

import aioble
aioble.security.load_secrets()        # default path: ble_secrets.json

Danach verwendet aioble beim nächsten Auftauchen eines gebondeten Peers die gespeicherten Schlüssel wieder, und die Verbindung wird ohne weiteren Handshake verschlüsselt.

Zwei praktische Konsequenzen des Speicherns von Schlüsseln im Flash:

  • Ein Gerät vergessen. Lösche ble_secrets.json (oder entferne den betreffenden Eintrag), um alle gebondeten Peers zu vergessen, und führe dann ein erneutes Pairing von Grund auf durch.

  • Physischer Zugriff gibt Schlüssel preis. Jeder mit Zugriff auf das Dateisystem der Kamera kann das JSON lesen. Dies ist dieselbe Art von Einschränkung, die auf der Netzwerkseite mit TLS-Schlüsseln auftrat (Betrieb: Schlüssel, Ablauf und Fehlerbehebung): verwende gerätespezifische Schlüssel, behandle jeden gespeicherten Schlüssel als wiederherstellbar und verlasse dich auf die Möglichkeit zum Widerruf (hier das Entfernen des Bonds auf der Central-Seite) statt darauf, dass der Schlüssel geheim bleibt.

11.13.5. Was Verschlüsselung garantiert – und was nicht

Eine Pair-then-Encrypt-Verbindung bietet, in der Reihenfolge ihrer Stärke:

  • Vertraulichkeit. Immer. Ein Lauscher kann die Bytes nicht lesen.

  • Integrität. Immer. Modifizierte Pakete bestehen die Authenticated-Encryption-Prüfung der Link-Schicht nicht und werden verworfen.

  • Authentifizierung. Nur mit mitm=True und einer geeigneten IO-Fähigkeit. Ohne sie könnte sich ein Man-in-the-Middle, der den ursprünglichen Pairing-Austausch abgefangen hat, eingeschleust haben; ohne MITM-Schutz gibt es für die beiden Seiten keine Möglichkeit, das zu erkennen.

Für die meisten Kamera-Anwendungsfälle – ein Telefon, das sich einmal mit der Kamera koppelt und sich später erneut verbindet – reicht mitm=False meist aus, weil das ursprüngliche Pairing in einer kontrollierten Umgebung stattfindet (der Benutzer hält beide Geräte im selben Raum). Für Anwendungen, bei denen ein gekoppeltes Gerät der Kamera zuerst über eine große Entfernung oder durch einen nicht vertrauenswürdigen Vermittler begegnen könnte, ist MITM die richtige Einstellung.

11.13.6. Wann Pairing die falsche Antwort ist

Pairing hat reale Kosten: ein paar Sekunden Austausch bei der ersten Verbindung, dauerhafte Flash-Nutzung für jedes gebondete Gerät und die Wiederherstellungsgeschichte des „Bonds vergessen“, falls etwas schiefgeht. Für wirklich öffentliche Daten – als Beacon veröffentlichte Umgebungssensorwerte, ein Schild, das seinen Namen anzeigt, alles, was die Welt nicht dadurch verändert, dass es gelesen oder geschrieben wird – ist die richtige Antwort, gar nicht zu verschlüsseln und jeden nahen Scanner die Werte lesen zu lassen.

Für alles andere ist connection.pair(bond=True) auf dem Central die einzeilige Ergänzung, die die Verbindung von einem öffentlichen Kanal in einen privaten verwandelt.