Enne kui Baby Monitor Timmy edastab heli ja videot, peavad kaks seadet teineteist leidma ning üksteist usaldama. See sidumise etapp on kogu protsessi kõige kriitilisem hetk. Siin selgitan, kuidas Timmy seadmeid seob, milline krüptograafia selle taga on ja miks lähedal olev ründaja ei saa ühendust märkamatult üle võtta.
Probleem: kuidas mu seade teab, kellega ta suhtleb?
Kui kaks seadet ühenduvad esimest korda, on keskne küsimus järgmine: kas seade A suhtleb tõesti seadmega B või on keegi nende vahel? Krüptograafias nimetatakse seda vahendusründeks (MITM).
Timmy lahendab selle elliptilise kõvera Diffie-Hellmani (ECDH) võtmevahetusega Firebase'i kaudu koos kasutajapoolse visuaalse kontrolliga.
Järgmine diagramm näitab kogu sidumisprotsessi ühe pilguga:
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
Täielik sidumisprotokolli järjestus — muudetav lähtefail: docs/diagrams/pairing-sequence.mmd
1. samm: iga seade loob võtmepaari
Sidumiskuvale minnes loob iga seade ajutise ECDH võtmepaari P-256 kõveral (secp256r1):
- Üks privaatvõti — jääb ainult seadmesse
- Üks avalik võti — vahetatakse Firebase'i kaudu
Võtmed luuakse krüptograafiliselt turvalise juhuarvugeneraatoriga (Random.secure())
ning need on kehtivad ainult selle ühe sidumiskatse jaoks. Iga uue katse jaoks luuakse
uued võtmed.
2. samm: avalike võtmete vahetamine Firebase'i kaudu
Selleks et kaks seadet teineteist leiaksid, kasutab Timmy 4-märgilist koodi kohtumispaigana. Seda koodi saab automaatselt tuvastada funktsiooniga Lähedalasuvad ühendused (Bluetooth Low Energy) või sisestada käsitsi. Sellel puudub krüptograafiline väärtus; see aitab vaid mõlemal seadmel leida sama Firebase Firestore'i dokumendi.
Kui mõlemad seadmed koodi teavad, kirjutab kumbki oma avaliku ECDH-võtme ühisesse Firestore'i dokumenti. Seejärel loeb kumbki seade sellest dokumendist teise seadme avaliku võtme.
Oluline: saadetakse ainult avalik võti. Privaatvõti ei lahku kunagi seadmest. Firebase'i liiklust jälgiv inimene näeb avalikke võtmeid, kuid ei saa nende põhjal arvutada ühist saladust . See tugineb elliptilise kõvera diskreetse logaritmi probleemi (ECDLP) keerukusele.
3. samm: ühise saladuse arvutamine
Kui mõlemad seadmed on teineteise avaliku võtme leidnud, arvutavad nad iseseisvalt sama ühise saladuse:
sharedSecret = ECDH(myPrivateKey, remotePublicKey)
→ 32 bytes (identical on both devices)
. Elliptiliste kõverate matemaatika tagab, et mõlemad arvutused annavad sama tulemuse, kuigi kumbki seade teab vaid oma privaatvõtit ja teise seadme avalikku võtit.
4. samm: kontrollnumber (SAS)
Ühisest saladusest tuletatakse lühike autentimisstring (SAS) — kahekohaline number, mida kuvatakse mõlemas seadmes:
hash = SHA-256("sas:" + sort(pubkeyA, pubkeyB) + sharedSecret)
number = (hash[0] × 256 + hash[1]) mod 100 → 00 to 99
Mõlemad seadmed kuvavad sama numbri — näiteks 42. Kasutaja võrdleb visuaalselt, kas mõlema ekraani numbrid kattuvad, ja kinnitab seejärel igas seadmes eraldi.
Miks ründaja ei saa seda võltsida
Vahendusründaja peaks Firebase'i kaudu toimuvasse võtmevahetusse sekkuma. Täpsemalt peaks ta:
- asendama Firestore'i dokumendis olevad päris avalikud võtmed enda omadega
- looma kummagi seadmega eraldi ühise saladuse
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)
Vahendusründe tuvastamine SAS-i mittevastavuse abil — muudetav lähtefail: docs/diagrams/mitm-detection.mmd
Sel juhul arvutab ründaja ühise saladuse S_A seadmega A ja
teistsuguse ühise saladuse S_B seadmega B. Kuna S_A ≠ S_B,
arvutavad seadmed erinevad kontrollnumbrid.
. Ründaja ei saa numbreid kattuma panna, sest:
- ta ei tea seadmete privaatvõtmeid
- SHA-256 ei ole pööratav
- juhusliku kokkulangevuse tõenäosus on vaid 1 100-st
. Kasutaja näeb ekraanidel erinevaid numbreid ja katkestab sidumise. Selleks hetkeks on rünne nähtavaks muutunud.
5. samm: sidumise lõpuleviimine
Alles pärast seda, kui kasutaja on kontrolli kinnitanud mõlemas seadmes , jõuab sidumine lõpule:
- Üks 64-märgiline sidumisvõti (256 bitti) tuletatakse ühisest saladusest:
SHA-256("pair:" + sharedSecret) → pairingKey - Dokumendivõti tuletatakse kui
SHA-256("doc:" + pairingKey)ning seda kasutatakse Firestore'i dokumendivõtmena - Krüptovõti tuletatakse kui
SHA-256("enc:" + pairingKey)ning see on krüptitud signaalimise AES-256-GCM-võtmeks - Mõlemad seadmed salvestavad sama sidumisvõtme ja liiguvad režiimi valimise juurde
Sellest hetkest alates on kõik järgmised ühenduskatsed (Firestore'i signaalimine, WebRTC seadistamine) krüptitud ühise AES-256-GCM-võtmega. Sidumisvõtit ei saadeta kunagi taustsüsteemi; dokumendi identifikaatorina kasutatakse ainult selle SHA-256 räsi.
Süsteemi arhitektuur
Järgmine diagramm näitab sidumises ja suhtluses osalevaid komponente:
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
Süsteemi arhitektuuri ülevaade — muudetav lähtefail: docs/diagrams/pairing-architecture.mmd
Suhtluskanalid üksikasjalikult:
- WebRTC võrdõigusvõrk (paks joon): heli, video ja DataChanneli andmed liiguvad otse seadmete vahel — DTLS-SRTP-ga krüptitult. Ükski server neid andmeid ei näe.
- Firebase Firestore (pidev joon): sidumisandmed (ECDH-võtmed) ja signaalimine (SDP/ICE) liiguvad Firestore'i kaudu — otsast otsani krüptituna AES-256-GCM-iga. Firebase ei saa andmeid dekrüptida.
- STUN-server: mõlemad seadmed tuvastavad oma avaliku IP-aadressi, et saaks luua otsese võrdõigusühenduse.
- TURN-i relee: kui otsene ühendus ei ole võimalik (nt mobiilse andmeside korral), vahendab valitud kohalik või Cloudflare'i TURN-server krüptitud meediavoogu. Lühiajalised pääsuandmed (24 h) hangitakse Firebase Cloud Functionsi kaudu.
- Bluetooth LE (punktiirjoon): Nearby Connections leiab läheduses olevad seadmed automaatselt — edastatakse ainult kohtumiskood, mitte võtmematerjali.
Varuvariant: koodi käsitsi sisestamine
Kui Bluetooth pole saadaval (nt vanemates seadmetes), saab 4-märgilise koodi sisestada ka käsitsi. Käsitsi sisestamisel kasutatakse sama ECDH võtmevahetust ja sama SAS-i kontrolli nagu automaatsel sidumisel. Ainus erinevus on see, et kasutaja loeb koodi ja sisestab selle ise, selle asemel et seade tuvastaks koodi BLE kaudu.
Kuna ECDH võtmevahetus toimub mõlemal juhul Firebase'i kaudu, on turvalisus identne. 4-märgiline kood on vaid kohtumispaik; tegelik krüptimine põhineb ECDH-st tuletatud 256-bitisel võtmel.
Kokkuvõte
| Turvamehhanism | Kaitseb selle eest |
|---|---|
| ECDH võtmevahetus (P-256) | Võtmevahetuse liikluse pealtkuulamine |
| Ajutised võtmepaarid | Edasisaladus — varasemad sidumised jäävad turvaliseks |
| Visuaalne kontrollnumber (SAS) | Vahendusrünne (MITM) võtmevahetuse ajal |
| SHA-256 räsi dokumendivõtmena | Koodi väljavõtmine Firestore'ist |
| AES-256-GCM krüptimine | Signaalimisandmete pealtkuulamine |
| Mõlemapoolne kinnitus | Ühepoolne sidumine kasutaja teadmata |
| DTLS-SRTP (WebRTC) | Heli/video pealtkuulamine |
Need kihid töötavad koos: ECDH kaitseb võtmevahetust, kontrollnumber kaitseb MITM-i eest, AES-256-GCM kaitseb signaalimist ja WebRTC kaitseb meediat. Ründaja peaks selle ahela mitmest kohast murdma, ilma et seadmed või vanemad seda märkaksid.