Bevor Babyphone Timmy Audio und Video überträgt, müssen sich zwei Geräte finden und einander vertrauen. Diese Kopplung ist der kritischste Moment im ganzen Ablauf. Hier erkläre ich, wie Timmy koppelt, welche Kryptografie dahinter steckt und warum ein Angreifer in der Nähe die Verbindung nicht unbemerkt übernehmen kann.
Das Problem: Woher weiß mein Gerät, mit wem es spricht?
Wenn zwei Geräte sich zum ersten Mal verbinden, gibt es eine zentrale Frage: Spricht Gerät A wirklich mit Gerät B, oder sitzt jemand dazwischen? In der Kryptografie heißt das Man-in-the-Middle-Angriff (MITM).
Timmy löst das mit einem Elliptic Curve Diffie-Hellman (ECDH) Schlüsselaustausch über Firebase und einer visuellen Verifikation durch den Benutzer.
Das folgende Diagramm zeigt den Kopplungsablauf auf einen Blick:
sequenceDiagram
autonumber
participant A as 📱 Gerät A
participant F as ☁️ Firebase
participant B as 📱 Gerät B
Note over A,B: Phase 1 — Entdeckung
A->>A: ECDH-Schlüsselpaar erzeugen (P-256)
B->>B: ECDH-Schlüsselpaar erzeugen (P-256)
alt Auto-Kopplung (Nearby BLE)
A-->>B: BLE-Broadcast: SBM:XKQM
B-->>A: BLE-Broadcast: SBM:R7NP
Note over A,B: Niedrigerer Code gewinnt → bestimmt Creator/Joiner
else Manuelle Kopplung
A->>A: 4-Zeichen-Code anzeigen
Note right of A: Benutzer liest Code ab
B->>B: Benutzer gibt Code ein
end
Note over A,B: Phase 2 — ECDH-Schlüsselaustausch
A->>F: Öffentlichen Schlüssel (PubA) schreiben
B->>F: Öffentlichen Schlüssel (PubB) schreiben
F-->>B: PubA lesen
F-->>A: PubB lesen
Note over A,B: Phase 3 — Gemeinsames Geheimnis
A->>A: sharedSecret = ECDH(privA, PubB)
B->>B: sharedSecret = ECDH(privB, PubA)
Note over A,B: Beide berechnen identisches 32-Byte-Geheimnis
A->>A: SAS = SHA-256("sas:" + sort(PubA,PubB) + secret) → 2-stellige Zahl
B->>B: SAS = SHA-256("sas:" + sort(PubA,PubB) + secret) → 2-stellige Zahl
Note over A,B: Phase 4 — Visuelle Verifikation
A->>A: SAS anzeigen: 42
B->>B: SAS anzeigen: 42
Note over A,B: 👤 Benutzer vergleicht Zahlen auf beiden Bildschirmen
A->>A: Benutzer bestätigt ✓
B->>B: Benutzer bestätigt ✓
Note over A,B: Phase 5 — Schlüsselableitung
A->>A: pairingKey = SHA-256("pair:" + secret)
B->>B: pairingKey = SHA-256("pair:" + secret)
A->>A: docKey = SHA-256("doc:" + pairingKey)
A->>A: encKey = SHA-256("enc:" + pairingKey)
Note over A,B: ✅ Gekoppelt — alle Signalisierung mit AES-256-GCM verschlüsselt
Vollständiges Kopplungsprotokoll — editierbare Quelle: docs/diagrams/pairing-sequence.mmd
Schritt 1: Jedes Gerät erzeugt ein Schlüsselpaar
Beim Öffnen des Kopplungsbildschirms erzeugt jedes Gerät ein ephemeres ECDH-Schlüsselpaar auf der Kurve P-256 (secp256r1):
- Einen privaten Schlüssel — bleibt ausschließlich auf dem Gerät
- Einen öffentlichen Schlüssel — wird über Firebase ausgetauscht
Die Schlüssel entstehen mit einem kryptografisch sicheren Zufallsgenerator (Random.secure())
und sind nur für diesen einen Kopplungsvorgang gültig. Bei jedem neuen Versuch
erzeugt Timmy neue Schlüssel.
Schritt 2: Austausch der öffentlichen Schlüssel über Firebase
Damit sich zwei Geräte finden, nutzt Timmy einen 4-stelligen Code als Treffpunkt. Dieser Code kann automatisch über Nearby Connections (Bluetooth Low Energy) entdeckt oder manuell eingegeben werden. Er hat keinerlei kryptografischen Wert; er sorgt nur dafür, dass beide Geräte dasselbe Firebase-Firestore-Dokument finden.
Sobald beide Geräte den Code kennen, schreiben sie ihren öffentlichen ECDH-Schlüssel in ein gemeinsames Firestore-Dokument. Danach liest jedes Gerät den öffentlichen Schlüssel des anderen aus diesem Dokument.
Entscheidend: Übertragen wird nur der öffentliche Schlüssel. Der private Schlüssel verlässt das Gerät niemals. Wer den Firebase-Verkehr abhört, sieht öffentliche Schlüssel, kann daraus aber das gemeinsame Geheimnis nicht berechnen. Das beruht auf der Schwierigkeit des Elliptic Curve Discrete Logarithm Problems (ECDLP).
Schritt 3: Berechnung des gemeinsamen Geheimnisses
Sobald beide Geräte den öffentlichen Schlüssel des jeweils anderen aus Firebase gelesen haben, berechnen sie unabhängig voneinander dasselbe gemeinsame Geheimnis (Shared Secret):
sharedSecret = ECDH(meinPrivaterSchlüssel, remoteÖffentlicherSchlüssel)
→ 32 Bytes (identisch auf beiden Geräten)
Die Mathematik elliptischer Kurven sorgt dafür, dass beide Berechnungen dasselbe Ergebnis liefern, obwohl jedes Gerät nur den eigenen privaten Schlüssel und den öffentlichen Schlüssel des anderen kennt.
Schritt 4: Die Verifikationszahl (SAS)
Aus dem gemeinsamen Geheimnis wird ein Short Authentication String (SAS) abgeleitet — eine zweistellige Zahl, die auf beiden Geräten angezeigt wird:
hash = SHA-256("sas:" + sort(pubkeyA, pubkeyB) + sharedSecret)
zahl = (hash[0] × 256 + hash[1]) mod 100 → 00 bis 99
Beide Geräte zeigen dieselbe Zahl an, zum Beispiel 42. Der Benutzer vergleicht die Zahlen auf beiden Bildschirmen und bestätigt auf jedem Gerät einzeln.
Warum ein Angreifer dies nicht fälschen kann
Ein Man-in-the-Middle müsste sich zwischen beide Geräte setzen und die öffentlichen Schlüssel in Firebase manipulieren. Konkret müsste er:
- Die echten öffentlichen Schlüssel in Firebase abfangen und ersetzen
- Eigene Schlüsselpaare mit jedem Gerät austauschen
sequenceDiagram
autonumber
participant A as 📱 Gerät A
participant M as 🕵️ Angreifer (MITM)
participant B as 📱 Gerät B
Note over A,B: Angreifer fängt den Firebase-Schlüsselaustausch ab
A->>A: Schlüsselpaar erzeugen (privA, PubA)
B->>B: Schlüsselpaar erzeugen (privB, PubB)
M->>M: ZWEI Schlüsselpaare erzeugen (privM1, PubM1) + (privM2, PubM2)
A->>M: PubA in Firebase schreiben
M->>M: PubA durch PubM1 ersetzen
M->>B: B liest PubM1 (hält es für PubA)
B->>M: PubB in Firebase schreiben
M->>M: PubB durch PubM2 ersetzen
M->>A: A liest PubM2 (hält es für PubB)
Note over A,B: Jedes Gerät berechnet ein ANDERES Shared Secret
A->>A: secret_A = ECDH(privA, PubM2)
M->>M: secret_A = ECDH(privM2, PubA)
M->>M: secret_B = ECDH(privM1, PubB)
B->>B: secret_B = ECDH(privB, PubM1)
Note over A,M: secret_A ≠ secret_B
A->>A: SAS_A = SHA-256("sas:" + sort(PubA,PubM2) + secret_A) → 73
B->>B: SAS_B = SHA-256("sas:" + sort(PubM1,PubB) + secret_B) → 18
rect rgb(255, 230, 230)
Note over A,B: ❌ Benutzer sieht UNTERSCHIEDLICHE Zahlen!
A->>A: Anzeige: 73
B->>B: Anzeige: 18
Note over A,B: 👤 Benutzer bemerkt Unterschied → bricht Kopplung ab
end
Note over A,B: 🛡️ Angriff erkannt — MITM kann SAS nicht fälschen (P = 1/100)
MITM-Erkennung durch SAS-Abweichung — editierbare Quelle: docs/diagrams/mitm-detection.mmd
In diesem Fall berechnet der Angreifer mit Gerät A ein Shared Secret S_A und mit Gerät B
ein anderes Shared Secret S_B. Da S_A ≠ S_B, berechnen die Geräte
unterschiedliche Verifikationszahlen.
Der Angreifer kann die Zahlen nicht gezielt gleich machen, da:
- Er die privaten Schlüssel der Geräte nicht kennt
- SHA-256 nicht umkehrbar ist
- Die Wahrscheinlichkeit einer zufälligen Übereinstimmung nur 1:100 beträgt
Der Benutzer sieht unterschiedliche Zahlen auf den Bildschirmen und bricht die Kopplung ab. Damit ist der Angriff sichtbar geworden.
Schritt 5: Kopplung abschließen
Erst wenn der Benutzer auf beiden Geräten die Verifikation bestätigt hat, wird die Kopplung abgeschlossen:
- Aus dem Shared Secret wird ein 64-Zeichen Pairing-Key (256 Bit) abgeleitet:
SHA-256("pair:" + sharedSecret) → pairingKey - Aus dem Pairing-Key wird der Firestore-Dokumentschlüssel abgeleitet:
SHA-256("doc:" + pairingKey) → documentKey - Ein weiterer Hash liefert den AES-256-GCM-Schlüssel für die verschlüsselte Signalisierung:
SHA-256("enc:" + pairingKey) → encryptionKey - Beide Geräte speichern denselben Pairing-Key und navigieren zur Modusauswahl
Ab diesem Moment sind alle weiteren Verbindungsversuche (Signalisierung über Firestore, WebRTC-Aufbau) mit dem gemeinsamen AES-256-GCM-Schlüssel verschlüsselt. Der Pairing-Key wird niemals an das Backend gesendet; nur sein SHA-256-Hash dient als Dokumentkennung.
Systemarchitektur
Das folgende Diagramm zeigt die Komponenten, die am Kopplungs- und Kommunikationsprozess beteiligt sind:
flowchart TB
BABY["📱 Kind-Handy
Baby-Modus"]
PARENT["📱 Eltern-Handy
Eltern-Modus"]
BABY <==>|"🔒 WebRTC Peer-to-Peer · DTLS-SRTP
Audio · Video · DataChannel"| PARENT
BABY -.-|"🔵 Bluetooth LE · Nearby
Auto-Entdeckung"| PARENT
subgraph FIREBASE["☁️ Firebase (Google Cloud)"]
direction LR
AUTH["🪪 Anonyme
Authentifizierung"]
FS["📄 Firestore
Kopplung + Signalisierung"]
CF["⚡ Cloud Functions
getTurnCredentials"]
end
BABY <-->|"🔐 AES-256-GCM verschlüsselt
SDP · ICE · ECDH-Schlüssel"| FS
FS <-->|"🔐 AES-256-GCM verschlüsselt
SDP · ICE · ECDH-Schlüssel"| PARENT
BABY -.->|Token| AUTH
PARENT -.->|Token| AUTH
STUN["📡 STUN-Server
stun.cloudflare.com:3478"]
TURN["🔄 TURN-Relay
lokal oder Cloudflare"]
BABY & PARENT -->|Kurzlebige Zugangsdaten| CF
CF -->|lokal zuerst, Cloudflare-Fallback| TURN
BABY & PARENT -.->|NAT-Traversal| STUN
BABY -.->|"Relay-Fallback"| TURN
TURN -.->|"Relay-Fallback"| PARENT
style BABY fill:#FBF6F0,stroke:#B5734A,stroke-width:2px
style PARENT fill:#FBF6F0,stroke:#B5734A,stroke-width:2px
style FIREBASE fill:#fff5f5,stroke:#E9B44C,stroke-width:2px
style AUTH fill:#E9B44C,stroke:#2B2D42
style FS fill:#E9B44C,stroke:#2B2D42
style CF fill:#E9B44C,stroke:#2B2D42
style STUN fill:#F6E3D2,stroke:#B5734A
style TURN fill:#7BC47F,stroke:#2B2D42
Systemarchitektur-Übersicht — editierbare Quelle: docs/diagrams/pairing-architecture.mmd
Die Kommunikationspfade im Detail:
- WebRTC Peer-to-Peer (dicke Linie): Audio, Video und DataChannel fliessen direkt zwischen den Geräten, verschlüsselt mit DTLS-SRTP. Kein Server sieht diese Daten.
- Firebase Firestore (durchgezogene Linie): Kopplungsdaten (ECDH-Schlüssel) und Signalisierung (SDP/ICE) laufen über Firestore, Ende-zu-Ende-verschlüsselt mit AES-256-GCM. Firebase kann die Daten nicht entschlüsseln.
- STUN-Server: Beide Geräte ermitteln ihre öffentliche IP-Adresse, damit eine direkte Peer-to-Peer-Verbindung aufgebaut werden kann.
- TURN-Relay: Falls eine direkte Verbindung nicht möglich ist (z.B. bei mobilem Internet), leitet der ausgewählte lokale oder Cloudflare-TURN-Server die verschlüsselten Medien weiter. Kurzlebige Zugangsdaten (24h) werden über Firebase Cloud Functions abgerufen.
- Bluetooth LE (gepunktete Linie): Nearby Connections entdeckt Geräte in der Nähe automatisch; übertragen wird nur der Meeting-Code, kein Schlüsselmaterial.
Fallback: Manuelle Codeeingabe
Falls Bluetooth nicht verfügbar ist (z.B. auf älteren Geräten), kann der 4-stellige Code auch manuell eingegeben werden. Die manuelle Eingabe nutzt denselben ECDH-Schlüsselaustausch und dieselbe SAS-Verifikation wie die automatische Kopplung. Der einzige Unterschied ist, dass der Code nicht per BLE entdeckt, sondern vom Benutzer abgelesen und eingetippt wird.
Weil der ECDH-Schlüsselaustausch in beiden Fällen über Firebase stattfindet, ist die Sicherheit identisch. Der 4-stellige Code dient nur als Treffpunkt; die eigentliche Verschlüsselung basiert auf dem 256-Bit-Schlüssel aus dem ECDH-Verfahren.
Zusammenfassung
| Schutzmechanismus | Schützt gegen |
|---|---|
| ECDH-Schlüsselaustausch (P-256) | Abhören des Firebase-Verkehrs |
| Ephemere Schlüsselpaare | Forward Secrecy — vergangene Kopplungen bleiben sicher |
| Visuelle Verifikationszahl (SAS) | Man-in-the-Middle (MITM) bei Firebase-Kopplung |
| SHA-256-Hash als Dokumentkennung | Code-Extraktion aus Firestore |
| AES-256-GCM-Verschlüsselung | Abhören der Signalisierungsdaten |
| Beidseitige Bestätigung | Einseitige Kopplung ohne Benutzerwissen |
| DTLS-SRTP (WebRTC) | Abhören von Audio/Video |
Die Schichten greifen ineinander: ECDH schützt den Schlüsselaustausch, die Verifikationszahl schützt vor MITM, AES-256-GCM schützt die Signalisierung und WebRTC schützt die Medien. Ein Angreifer müsste diese Kette an mehreren Stellen brechen, ohne dass Geräte oder Eltern es merken.