Než Baby Monitor Timmy začne přenášet zvuk a video, musí se dvě zařízení najít a vzájemně si důvěřovat. Tento krok párování je nejdůležitějším momentem celého procesu. Vysvětlím zde, jak Timmy zařízení páruje, jaká kryptografie za tím stojí a proč útočník v okolí nemůže spojení nepozorovaně převzít.
Problém: Jak moje zařízení ví, s kým komunikuje?
Když se dvě zařízení připojují poprvé, hlavní otázka zní: komunikuje zařízení A opravdu se zařízením B, nebo je mezi nimi někdo další? V kryptografii se tomu říká útok man-in-the-middle (MITM).
Timmy to řeší pomocí výměny klíčů Elliptic Curve Diffie-Hellman (ECDH) přes Firebase v kombinaci s vizuálním ověřením uživatelem.
Následující diagram ukazuje celý postup párování v kostce:
sequenceDiagram
autonumber
participant A as 📱 Device A
participant F as ☁️ Firebase
participant B as 📱 Device B
Note over A,B: Phase 1 — Discovery
A->>A: Generate ECDH key pair (P-256)
B->>B: Generate ECDH key pair (P-256)
alt Auto-Pairing (Nearby BLE)
A-->>B: BLE broadcast: SBM:XKQM
B-->>A: BLE broadcast: SBM:R7NP
Note over A,B: Lower code wins → determines creator/joiner
else Manual Pairing
A->>A: Display 4-char code
Note right of A: User reads code
B->>B: User enters code
end
Note over A,B: Phase 2 — ECDH Key Exchange
A->>F: Write public key (PubA) to meeting doc
B->>F: Write public key (PubB) to meeting doc
F-->>B: Read PubA
F-->>A: Read PubB
Note over A,B: Phase 3 — Shared Secret
A->>A: sharedSecret = ECDH(privA, PubB)
B->>B: sharedSecret = ECDH(privB, PubA)
Note over A,B: Both compute identical 32-byte secret
A->>A: SAS = SHA-256("sas:" + sort(PubA,PubB) + secret) → 2-digit number
B->>B: SAS = SHA-256("sas:" + sort(PubA,PubB) + secret) → 2-digit number
Note over A,B: Phase 4 — Visual Verification
A->>A: Display SAS: 42
B->>B: Display SAS: 42
Note over A,B: 👤 User compares numbers on both screens
A->>A: User confirms ✓
B->>B: User confirms ✓
Note over A,B: Phase 5 — Key Derivation
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: ✅ Paired — all future signaling encrypted with AES-256-GCM
Kompletní sekvence párovacího protokolu — upravitelný zdroj: docs/diagrams/pairing-sequence.mmd
Krok 1: Každé zařízení vytvoří pár klíčů
Po otevření obrazovky párování si každé zařízení vytvoří dočasný pár klíčů ECDH na křivce P-256 (secp256r1):
- Soukromý klíč — zůstává výhradně v zařízení
- Veřejný klíč — vyměňuje se přes Firebase
Klíče se vytvářejí pomocí kryptograficky bezpečného generátoru náhodných čísel (Random.secure())
a jsou platné jen pro tento jediný pokus o párování. Pro každý nový pokus se vytvoří nové klíče.
Krok 2: Výměna veřejných klíčů přes Firebase
Aby se dvě zařízení našla, používá Timmy 4znakový kód jako společný bod setkání. Tento kód lze automaticky zjistit pomocí Nearby Connections (Bluetooth Low Energy) nebo zadat ručně. Nemá žádnou kryptografickou hodnotu; pouze zajistí, že obě zařízení najdou stejný dokument ve Firebase Firestore.
Jakmile obě zařízení znají kód, každé zapíše svůj veřejný klíč ECDH do sdíleného dokumentu Firestore. Poté každé zařízení z tohoto dokumentu načte veřejný klíč druhého zařízení.
Důležité: odesílá se pouze veřejný klíč. Soukromý klíč zařízení nikdy neopustí. Kdokoli sleduje provoz Firebase, vidí veřejné klíče, ale nemůže z nich vypočítat sdílené tajemství . To vychází z obtížnosti problému diskrétního logaritmu na eliptické křivce (ECDLP).
Krok 3: Výpočet sdíleného tajemství
Jakmile obě zařízení zjistí veřejný klíč toho druhého, nezávisle vypočítají stejné sdílené tajemství:
sharedSecret = ECDH(myPrivateKey, remotePublicKey)
→ 32 bytes (identical on both devices)
Matematika eliptických křivek zaručuje, že oba výpočty dají stejný výsledek, přestože každé zařízení zná jen svůj soukromý klíč a veřejný klíč druhého zařízení.
Krok 4: Ověřovací číslo (SAS)
Ze sdíleného tajemství se odvodí krátký ověřovací řetězec (SAS) — dvouciferné číslo zobrazené na obou zařízeních:
hash = SHA-256("sas:" + sort(pubkeyA, pubkeyB) + sharedSecret)
number = (hash[0] × 256 + hash[1]) mod 100 → 00 to 99
Obě zařízení zobrazí stejné číslo — například 42. Uživatel vizuálně zkontroluje, zda se čísla na obou obrazovkách shodují, a pak potvrzení provede na každém zařízení zvlášť.
Proč to útočník nemůže zfalšovat
Útočník typu man-in-the-middle by musel zachytit výměnu klíčů ve Firebase. Konkrétně by musel:
- Nahradit skutečné veřejné klíče uložené v dokumentu Firestore svými vlastními
- Navázat s každým zařízením samostatné sdílené tajemství
sequenceDiagram
autonumber
participant A as 📱 Device A
participant M as 🕵️ Attacker (MITM)
participant B as 📱 Device B
Note over A,B: Attacker intercepts the Firebase key exchange
A->>A: Generate key pair (privA, PubA)
B->>B: Generate key pair (privB, PubB)
M->>M: Generate TWO key pairs (privM1, PubM1) + (privM2, PubM2)
A->>M: Write PubA to Firebase
M->>M: Replace PubA with PubM1
M->>B: B reads PubM1 (thinks it is PubA)
B->>M: Write PubB to Firebase
M->>M: Replace PubB with PubM2
M->>A: A reads PubM2 (thinks it is PubB)
Note over A,B: Each device computes a DIFFERENT 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: ❌ User sees DIFFERENT numbers!
A->>A: Display: 73
B->>B: Display: 18
Note over A,B: 👤 User notices mismatch → cancels pairing
end
Note over A,B: 🛡️ Attack detected — MITM cannot force SAS match (P = 1/100)
Odhalení man-in-the-middle pomocí neshody SAS — upravitelný zdroj: docs/diagrams/mitm-detection.mmd
V tomto případě útočník vypočítá sdílené tajemství S_A se zařízením A a
jiné sdílené tajemství S_B se zařízením B. Protože S_A ≠ S_B,
zařízení vypočítají různá ověřovací čísla.
Útočník nemůže zařídit, aby se čísla shodovala, protože:
- Nezná soukromé klíče zařízení
- SHA-256 nelze obrátit
- Pravděpodobnost náhodné shody je jen 1 ze 100
Uživatel na obrazovkách uvidí různá čísla a párování zruší. V tu chvíli je útok odhalen.
Krok 5: Dokončení párování
Párování se dokončí až poté, co uživatel potvrdí ověření na obou zařízeních :
- Ze sdíleného tajemství se odvodí 64znakový párovací klíč (256 bitů) :
SHA-256("pair:" + sharedSecret) → pairingKey - Klíč dokumentu se odvodí jako
SHA-256("doc:" + pairingKey)a slouží jako klíč dokumentu Firestore - Šifrovací klíč se odvodí jako
SHA-256("enc:" + pairingKey)a poskytuje klíč AES-256-GCM pro šifrovanou signalizaci - Obě zařízení uloží stejný párovací klíč a přejdou k výběru režimu
Od této chvíle jsou všechny další pokusy o připojení (signalizace přes Firestore, nastavení WebRTC) šifrované sdíleným klíčem AES-256-GCM. Párovací klíč se nikdy neodesílá do backendu; jako identifikátor dokumentu se používá pouze jeho hash SHA-256.
Architektura systému
Následující diagram ukazuje komponenty zapojené do párování a komunikace:
flowchart TB
BABY["📱 Baby Phone
Baby Mode"]
PARENT["📱 Parent Phone
Parent Mode"]
BABY <==>|"🔒 WebRTC Peer-to-Peer · DTLS-SRTP
Audio · Video · DataChannel"| PARENT
BABY -.-|"🔵 Bluetooth LE · Nearby
Auto-Discovery"| PARENT
subgraph FIREBASE["☁️ Firebase (Google Cloud)"]
direction LR
AUTH["🪪 Anonymous
Authentication"]
FS["📄 Firestore
Pairing + Signaling"]
CF["⚡ Cloud Functions
getTurnCredentials"]
end
BABY <-->|"🔐 AES-256-GCM encrypted
SDP · ICE · ECDH keys"| FS
FS <-->|"🔐 AES-256-GCM encrypted
SDP · ICE · ECDH keys"| PARENT
BABY -.->|Token| AUTH
PARENT -.->|Token| AUTH
STUN["📡 STUN server
stun.cloudflare.com:3478"]
TURN["🔄 TURN relay
local or Cloudflare"]
BABY & PARENT -->|Short-lived credentials| CF
CF -->|local first, 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
Přehled architektury systému — upravitelný zdroj: docs/diagrams/pairing-architecture.mmd
Komunikační cesty podrobně:
- WebRTC peer-to-peer (silná čára): Zvuk, video a DataChannel proudí přímo mezi zařízeními — šifrovaně pomocí DTLS-SRTP. Žádný server tato data nevidí.
- Firebase Firestore (plná čára): Párovací data (klíče ECDH) a signalizace (SDP/ICE) procházejí přes Firestore — end-to-end šifrovaně pomocí AES-256-GCM. Firebase data nemůže dešifrovat.
- Server STUN: Obě zařízení zjistí svou veřejnou IP adresu, aby mohlo vzniknout přímé peer-to-peer spojení.
- Relé TURN: Pokud přímé spojení není možné (např. přes mobilní data), vybraný místní server TURN nebo Cloudflare TURN přeposílá šifrovaná média. Krátkodobé přihlašovací údaje (24 h) se načítají přes Firebase Cloud Functions.
- Bluetooth LE (tečkovaná čára): Nearby Connections automaticky objeví zařízení v okolí — přenáší se jen kód setkání, žádný klíčový materiál.
Záložní možnost: Ruční zadání kódu
Pokud Bluetooth není k dispozici (např. u starších zařízení), lze 4znakový kód zadat i ručně. Ruční zadání používá stejnou výměnu klíčů ECDH i stejné ověření SAS jako automatické párování. Jediný rozdíl je v tom, že kód uživatel přečte a zadá místo toho, aby byl zjištěn přes BLE.
Protože výměna klíčů ECDH v obou případech probíhá přes Firebase, je zabezpečení stejné. 4znakový kód slouží jen jako bod setkání; skutečné šifrování vychází z 256bitového klíče odvozeného z ECDH.
Shrnutí
| Bezpečnostní mechanismus | Chrání před |
|---|---|
| Výměna klíčů ECDH (P-256) | Odposlechem provozu při výměně klíčů |
| Dočasné páry klíčů | Dopředné utajení — minulá párování zůstávají v bezpečí |
| Vizuální ověřovací číslo (SAS) | Man-in-the-middle (MITM) během výměny klíčů |
| Hash SHA-256 jako klíč dokumentu | Získáním kódu z Firestore |
| Šifrování AES-256-GCM | Odposlechem signalizačních dat |
| Potvrzení na obou stranách | Jednostranným párováním bez vědomí uživatele |
| DTLS-SRTP (WebRTC) | Odposlechem zvuku/videa |
Tyto vrstvy do sebe zapadají: ECDH chrání výměnu klíčů, ověřovací číslo chrání před MITM, AES-256-GCM chrání signalizaci a WebRTC chrání média. Útočník by musel tento řetězec narušit na několika místech, aniž by si toho zařízení nebo rodiče všimli.