Mielőtt a Baby Monitor Timmy hangot és videót továbbítana, a két eszköznek meg kell találnia egymást, és meg kell bíznia egymásban. Ez a párosítási lépés a teljes folyamat legkritikusabb része. Itt bemutatom, hogyan végzi a Timmy a párosítást, milyen kriptográfiai megoldások állnak mögötte, és miért nem tud egy közeli támadó észrevétlenül átvenni a kapcsolatot.
A probléma: honnan tudja az eszközöm, kivel beszél?
Amikor két eszköz először kapcsolódik egymáshoz, a központi kérdés ez: az A eszköz valóban a B eszközzel kommunikál, vagy valaki közéjük ékelődött? A kriptográfiában ezt közbeékelődéses támadásnak (MITM) nevezik.
Timmy ezt egy elliptikus görbéken alapuló Diffie–Hellman-féle (ECDH-) kulcscserével oldja meg Firebase-en keresztül, kiegészítve a felhasználó általi vizuális ellenőrzéssel.
Az alábbi ábra egy pillantással bemutatja a teljes párosítási folyamatot:
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
A teljes párosítási protokoll folyamata — szerkeszthető forrás: docs/diagrams/pairing-sequence.mmd
1. lépés: mindkét eszköz kulcspárt hoz létre
A párosítási képernyő megnyitásakor mindkét eszköz létrehoz egy ideiglenes ECDH-kulcspárt a P-256-os görbén (secp256r1):
- Egy privát kulcs — kizárólag az eszközön marad
- Egy nyilvános kulcs — Firebase-en keresztül cserélődik ki
A kulcsok kriptográfiailag biztonságos véletlenszám-generátorral készülnek (Random.secure())
és csak ehhez az egyetlen párosítási kísérlethez érvényesek. Minden új kísérlethez új kulcsok jönnek létre.
2. lépés: nyilvános kulcsok cseréje Firebase-en keresztül
Hogy a két eszköz megtalálja egymást, Timmy egy 4 karakteres kódot használ találkozási pontként. Ez a kód automatikusan felismerhető a Nearby Connections (Bluetooth Low Energy) segítségével, vagy kézzel is beírható. Nincs kriptográfiai értéke; csak azt segíti, hogy mindkét eszköz ugyanazt a Firebase Firestore-dokumentumot találja meg.
Miután mindkét eszköz ismeri a kódot, mindegyik elhelyezi a nyilvános ECDH-kulcsát egy közös Firestore-dokumentumba. Ezután mindkét eszköz kiolvassa a másik eszköz nyilvános kulcsát ebből a dokumentumból.
Lényeges: csak a nyilvános kulcs kerül elküldésre. A privát kulcs soha nem hagyja el az eszközt. Aki figyeli a Firebase-forgalmat, nyilvános kulcsokat lát, de nem tudja kiszámítani a közös titkot belőlük. Ez az elliptikus görbe diszkrét logaritmus problémájának (ECDLP) nehézségére épül.
3. lépés: a közös titok kiszámítása
Miután mindkét eszköz megkapta a másik nyilvános kulcsát, egymástól függetlenül ugyanazt a közös titkot:
sharedSecret = ECDH(myPrivateKey, remotePublicKey)
→ 32 bytes (identical on both devices)
számítják ki. Az elliptikus görbék matematikája garantálja, hogy a két számítás eredménye azonos lesz, noha minden eszköz csak a saját privát kulcsát és a másik nyilvános kulcsát ismeri.
4. lépés: az ellenőrző szám (SAS)
A közös titokból egy rövid hitelesítő karakterlánc (SAS) származik — egy kétjegyű szám, amely mindkét eszközön megjelenik:
hash = SHA-256("sas:" + sort(pubkeyA, pubkeyB) + sharedSecret)
number = (hash[0] × 256 + hash[1]) mod 100 → 00 to 99
Mindkét eszköz ugyanazt a számot mutatja — például 42. A felhasználó szemmel összehasonlítja, egyeznek-e a két képernyőn lévő számok, majd mindkét eszközön külön megerősíti azt.
Miért nem tudja ezt egy támadó meghamisítani?
Egy közbeékelődéses támadónak el kellene fognia a Firebase-en keresztül zajló kulcscserét. Ehhez konkrétan a következőket kellene tennie:
- A Firestore-dokumentumban tárolt valódi nyilvános kulcsokat a sajátjaira cserélni
- Mindkét eszközzel külön közös titkot létrehozni
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)
Közbeékelődéses támadás felismerése SAS-eltérés alapján — szerkeszthető forrás: docs/diagrams/mitm-detection.mmd
Ebben az esetben a támadó egy közös titkot számít ki S_A az A eszközzel és egy
másik közös titkot S_B a B eszközzel. Mivel S_A ≠ S_B,
az eszközök eltérő ellenőrző számokat.
számítanak ki. A támadó nem tudja elérni, hogy a számok egyezzenek, mert:
- Nem ismeri az eszközök privát kulcsait
- A SHA-256 egyirányú hashfüggvény
- Véletlen egyezés esélye mindössze 1 a 100-ból
A felhasználó eltérő számokat lát a képernyőkön, és megszakítja a párosítást. Ekkor a támadás láthatóvá válik.
5. lépés: a párosítás befejezése
A párosítás csak azután fejeződik be, hogy a felhasználó mindkét eszközön megerősítette az ellenőrzést:
- Egy 64 karakterből álló párosítási kulcs (256 bit) származik a közös titokból:
SHA-256("pair:" + sharedSecret) → pairingKey - A dokumentumkulcs származtatása:
SHA-256("doc:" + pairingKey)Ez szolgál a Firestore-dokumentum kulcsaként - A titkosítási kulcs származtatása:
SHA-256("enc:" + pairingKey)Ez adja a titkosított jelzésforgalomhoz használt AES-256-GCM-kulcsot - Mindkét eszköz eltárolja ugyanazt a párosítási kulcsot, majd továbblép a módválasztáshoz
Ettől kezdve minden további kapcsolódási kísérlet jelzésforgalma (a Firestore-on keresztüli jelzésváltás és a WebRTC-kapcsolat felépítése) a közös AES-256-GCM-kulccsal van titkosítva. A párosítási kulcsot soha nem küldik el a háttérrendszernek; dokumentumazonosítóként csak a SHA-256-hashét használják.
Rendszerarchitektúra
Az alábbi ábra a párosításban és kommunikációban részt vevő összetevőket mutatja be:
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
Rendszerarchitektúra áttekintése — szerkeszthető forrás: docs/diagrams/pairing-architecture.mmd
A kommunikációs útvonalak részletesen:
- WebRTC peer-to-peer (vastag vonal): A hang, a videó és a DataChannel közvetlenül az eszközök között áramlik — DTLS-SRTP-vel titkosítva. Ezt az adatot egyetlen szerver sem látja.
- Firebase Firestore (folytonos vonal): A párosítási adatok (ECDH-kulcsok) és a jelzésforgalom (SDP/ICE) a Firestore-on keresztül haladnak — végpontok közötti AES-256-GCM-titkosítással. A Firebase nem tudja visszafejteni az adatokat.
- STUN-szerver: Mindkét eszköz megállapítja a saját nyilvános IP-címét, hogy közvetlen peer-to-peer kapcsolat jöhessen létre.
- TURN-relé: Ha nem lehetséges közvetlen kapcsolat (például mobiladat-kapcsolaton keresztül), a kiválasztott helyi vagy Cloudflare TURN-szerver továbbítja a titkosított médiát. A rövid élettartamú, 24 óráig érvényes hitelesítő adatokat a rendszer Firebase Cloud Functions-függvényeken keresztül kéri le.
- Bluetooth LE (szaggatott vonal): A Nearby Connections automatikusan felismeri a közeli eszközöket — csak a találkozási kód kerül továbbításra, kulcsanyag nem.
Tartalék megoldás: kód kézi beírása
Ha a Bluetooth nem érhető el (például régebbi eszközökön), a 4 karakteres kód kézzel is beírható. A kézi beírás ugyanazt az ECDH-kulcscserét és ugyanazt a SAS-ellenőrzést használja, mint az automatikus párosítás. Az egyetlen különbség az, hogy a kódot a felhasználó olvassa le és írja be, ahelyett, hogy az eszköz BLE-n keresztül automatikusan észlelné.
Mivel az ECDH-kulcsere mindkét esetben Firebase-en keresztül történik, a biztonság azonos. A 4 karakteres kód csak találkozási pont; a tényleges titkosítás az ECDH-ból származó 256 bites kulcson alapul.
Összefoglalás
| Biztonsági mechanizmus | Védelmet nyújt ez ellen |
|---|---|
| ECDH-kulcscsere (P-256) | Kulcscsere-forgalom lehallgatása |
| Ideiglenes kulcspárok | Előremenő titkosság — a korábbi párosítások biztonságban maradnak |
| Vizuális ellenőrző szám (SAS) | Közbeékelődéses támadás (MITM) kulcscsere közben |
| SHA-256-hash dokumentumkulcsként | Kód kinyerése Firestore-ból |
| AES-256-GCM titkosítás | A jelzésforgalom lehallgatása |
| Kétoldali megerősítés | Egyoldalú párosítás a felhasználó tudta nélkül |
| DTLS-SRTP (WebRTC) | Hang- és videólehallgatás |
Ezek a rétegek együtt működnek: az ECDH védi a kulcscserét, az ellenőrző szám a MITM-támadások ellen véd, az AES-256-GCM a jelzésforgalmat, a WebRTC pedig a médiaforgalmat védi. Egy támadónak ezt a védelmi láncot több ponton is fel kellene törnie anélkül, hogy az eszközök vagy a szülők észrevennék.