Bevor Babyphone Timmy Audio und Video übertragen kann, müssen sich zwei Geräte finden und gegenseitig vertrauen. Dieser Vorgang — die Kopplung — ist der sicherheitskritischste Moment im gesamten Ablauf. In diesem Artikel erklären wir Schritt für Schritt, wie die Kopplung funktioniert, welches kryptografische Verfahren dahinter steckt und warum ein Angreifer in der Nähe die Verbindung nicht unbemerkt kapern kann.
Das Problem: Woher weiß mein Gerät, mit wem es spricht?
Wenn zwei Geräte sich zum ersten Mal verbinden, stehen sie vor einer grundlegenden Frage: Woher weiß Gerät A, dass es wirklich mit Gerät B spricht — und nicht mit einem Angreifer, der sich dazwischengeschaltet hat? Dieses Problem ist in der Kryptografie als Man-in-the-Middle-Angriff (MITM) bekannt.
Timmy löst dieses Problem durch einen Elliptic Curve Diffie-Hellman (ECDH) Schlüsselaustausch über Firebase, kombiniert mit einer visuellen Verifikation durch den Benutzer.
Das folgende Diagramm zeigt den gesamten 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 generiert jedes Gerät ein ephemeres ECDH-Schlüsselpaar auf der Kurve P-256 (secp256r1):
- Einen privaten Schlüssel — bleibt ausschliesslich auf dem Gerät
- Einen öffentlichen Schlüssel — wird über Firebase ausgetauscht
Die Schlüssel werden mit einem kryptografisch sicheren Zufallsgenerator (Random.secure())
erzeugt und sind nur für diesen einen Kopplungsvorgang gültig. Bei jedem
erneuten Versuch werden neue Schlüssel generiert.
Schritt 2: Austausch der öffentlichen Schlüssel über Firebase
Damit sich zwei Geräte finden können, nutzt Timmy einen 4-stelligen Code als Treffpunkt. Dieser Code kann automatisch über Nearby Connections (Bluetooth Low Energy) entdeckt oder manuell eingegeben werden. Der Code hat keinerlei kryptografischen Wert — er dient ausschliesslich dazu, 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. Jedes Gerät liest dann den öffentlichen Schlüssel des anderen aus diesem Dokument.
Entscheidend: Nur der öffentliche Schlüssel wird übertragen. Der private Schlüssel verlässt das Gerät niemals. Ein Angreifer, der den Firebase-Verkehr abhört, sieht nur die öffentlichen Schlüssel — und kann daraus das gemeinsame Geheimnis nicht berechnen. Dies basiert auf der mathematischen 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 der elliptischen Kurven garantiert, dass beide Berechnungen dasselbe Ergebnis liefern, obwohl jedes Gerät nur seinen 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 visuell, ob die Zahlen auf beiden Bildschirmen übereinstimmen, 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. Der Angriff ist gescheitert.
Schritt 5: Kopplung abschliessen
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 alle Komponenten, die am Kopplungs- und Kommunikationsprozess beteiligt sind, und wie sie zusammenspielen:
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:#f0f7ff,stroke:#6BAFB2,stroke-width:2px
style PARENT fill:#f0f7ff,stroke:#6BAFB2,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:#D4EEEF,stroke:#6BAFB2
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 — nur der Meeting-Code wird übertragen, 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. Wichtig: 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.
Da 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 |
Jede Sicherheitsebene ergänzt die anderen. ECDH schützt den Schlüsselaustausch. Die Verifikationszahl schützt vor MITM. AES-256-GCM schützt die Signalisierung. WebRTC schützt die Kommunikation. Zusammen bilden sie eine Kette, die ein Angreifer an keiner Stelle unbemerkt durchbrechen kann.