Prije nego što Baby Monitor Timmy počne prenositi zvuk i video, dva se uređaja moraju pronaći i uspostaviti međusobno povjerenje. Taj postupak uparivanja najvažniji je trenutak cijelog procesa. Ovdje objašnjavam kako se Timmy uparuje, koja kriptografija stoji iza toga i zašto napadač u blizini ne može neprimjetno preuzeti vezu.
Problem: Kako moj uređaj zna s kim razgovara?
Kad se dva uređaja prvi put povezuju, ključno je pitanje: razgovara li uređaj A zaista s uređajem B ili se netko nalazi između njih? U kriptografiji se to naziva napad čovjeka u sredini (MITM).
Timmy to rješava razmjenom ključeva Elliptic Curve Diffie-Hellman (ECDH) putem Firebasea, u kombinaciji s vizualnom provjerom korisnika.
Sljedeći dijagram prikazuje cijeli postupak uparivanja na prvi pogled:
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
Cjeloviti slijed protokola za uparivanje — izvor koji se može uređivati: docs/diagrams/pairing-sequence.mmd
Korak 1: Svaki uređaj stvara par ključeva
Pri otvaranju zaslona za uparivanje svaki uređaj stvara privremeni ECDH par ključeva na krivulji P-256 (secp256r1):
- Privatni ključ — ostaje isključivo na uređaju
- Javni ključ — razmjenjuje se putem Firebasea
Ključevi se stvaraju kriptografski sigurnim generatorom slučajnih brojeva (Random.secure())
i vrijede samo za ovaj pojedinačni pokušaj uparivanja. Za svaki novi pokušaj stvaraju se novi ključevi.
Korak 2: Razmjena javnih ključeva putem Firebasea
Kako bi se dva uređaja pronašla, Timmy koristi kod od 4 znaka kao mjesto susreta. Taj se kod može automatski otkriti putem Nearby Connections (Bluetooth Low Energy) ili unijeti ručno. On nema kriptografsku vrijednost; samo omogućuje da oba uređaja pronađu isti dokument u Firebase Firestoreu.
Kad oba uređaja znaju kod, svaki zapisuje svoj javni ECDH ključ u zajednički Firestore dokument. Zatim svaki uređaj iz tog dokumenta čita javni ključ drugog uređaja.
Važno: šalje se samo javni ključ. Privatni ključ nikada ne napušta uređaj. Svatko tko prati promet putem Firebasea vidi javne ključeve, ali ne može izračunati zajedničku tajnu iz njih. To se temelji na težini problema diskretnog logaritma na eliptičkoj krivulji (ECDLP).
Korak 3: Izračun zajedničke tajne
Kad oba uređaja otkriju javni ključ onog drugog, neovisno izračunavaju istu zajedničku tajnu:
sharedSecret = ECDH(myPrivateKey, remotePublicKey)
→ 32 bytes (identical on both devices)
Matematika eliptičkih krivulja jamči da oba izračuna daju isti rezultat, iako svaki uređaj zna samo vlastiti privatni ključ i javni ključ drugog uređaja.
Korak 4: Broj za provjeru (SAS)
Iz zajedničke tajne izvodi se kratki autentifikacijski niz (SAS) — dvoznamenkasti broj prikazan na oba uređaja:
hash = SHA-256("sas:" + sort(pubkeyA, pubkeyB) + sharedSecret)
number = (hash[0] × 256 + hash[1]) mod 100 → 00 to 99
Oba uređaja prikazuju isti broj — na primjer, 42. Korisnik vizualno uspoređuje podudaraju li se brojevi na oba zaslona, a zatim potvrđuje na svakom uređaju zasebno.
Zašto napadač to ne može krivotvoriti
Napadač koji izvodi napad čovjeka u sredini morao bi presresti razmjenu ključeva u Firebaseu. Konkretno, morao bi:
- Zamijeniti stvarne javne ključeve pohranjene u Firestore dokumentu vlastitima
- Uspostaviti zasebne zajedničke tajne sa svakim uređajem
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)
Otkrivanje napada čovjeka u sredini zbog nepodudaranja SAS-a — izvor koji se može uređivati: docs/diagrams/mitm-detection.mmd
U ovom slučaju napadač izračunava zajedničku tajnu S_A s uređajem A i
drugu zajedničku tajnu S_B s uređajem B. Budući da S_A ≠ S_B,
uređaji izračunavaju različite brojeve za provjeru.
Napadač ne može učiniti da se brojevi podudaraju jer:
- Ne zna privatne ključeve uređaja
- SHA-256 nije reverzibilan
- Vjerojatnost slučajnog podudaranja iznosi samo 1 od 100
Korisnik vidi različite brojeve na zaslonima i prekida uparivanje. U tom je trenutku napad postao vidljiv.
Korak 5: Dovršavanje uparivanja
Tek nakon što korisnik potvrdi provjeru na oba uređaja uparivanje se dovršava:
- Ključ za uparivanje od 64 znaka (256 bita) izvodi se iz zajedničke tajne:
SHA-256("pair:" + sharedSecret) → pairingKey - Ključ dokumenta izvodi se kao
SHA-256("doc:" + pairingKey)i služi kao ključ Firestore dokumenta - Ključ za šifriranje izvodi se kao
SHA-256("enc:" + pairingKey)i pruža AES-256-GCM ključ za šifriranu signalizaciju - Oba uređaja pohranjuju isti ključ za uparivanje i prelaze na odabir načina rada
Od tog trenutka svi daljnji pokušaji povezivanja (Firestore signalizacija, postavljanje WebRTC-a) šifrirani su zajedničkim AES-256-GCM ključem. Ključ za uparivanje nikada se ne šalje na pozadinski sustav; samo se njegov SHA-256 hash koristi kao identifikator dokumenta.
Arhitektura sustava
Sljedeći dijagram prikazuje komponente uključene u uparivanje i komunikaciju:
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
Pregled arhitekture sustava — izvor koji se može uređivati: docs/diagrams/pairing-architecture.mmd
Komunikacijski putevi detaljno:
- WebRTC peer-to-peer (debela linija): zvuk, video i DataChannel teku izravno između uređaja — šifrirani DTLS-SRTP-om. Nijedan poslužitelj ne vidi te podatke.
- Firebase Firestore (puna linija): podaci za uparivanje (ECDH ključevi) i signalizacija (SDP/ICE) prolaze kroz Firestore — šifrirani s kraja na kraj pomoću AES-256-GCM-a. Firebase ne može dešifrirati podatke.
- STUN poslužitelj: oba uređaja otkrivaju svoju javnu IP adresu kako bi se mogla uspostaviti izravna peer-to-peer veza.
- TURN relej: ako izravna veza nije moguća (npr. preko mobilnih podataka), odabrani lokalni ili Cloudflare TURN poslužitelj prosljeđuje šifrirani medijski sadržaj. Kratkoročne vjerodajnice (24 h) dohvaćaju se putem Firebase Cloud Functionsa.
- Bluetooth LE (isprekidana linija): Nearby Connections automatski otkriva uređaje u blizini — prenosi se samo kod za susret, bez ključnog materijala.
Rezervna opcija: ručni unos koda
Ako Bluetooth nije dostupan (npr. na starijim uređajima), kod od 4 znaka može se unijeti i ručno. Ručni unos koristi istu ECDH razmjenu ključeva i istu SAS provjeru kao automatsko uparivanje. Jedina je razlika što korisnik pročita i upiše kod umjesto da ga otkrije putem BLE-a.
Budući da se ECDH razmjena ključeva u oba slučaja odvija putem Firebasea, sigurnost je identična. Kod od 4 znaka samo je mjesto susreta; pravo se šifriranje temelji na 256-bitnom ključu izvedenom iz ECDH-a.
Sažetak
| Sigurnosni mehanizam | Štiti od |
|---|---|
| ECDH razmjena ključeva (P-256) | Prisluškivanja prometa pri razmjeni ključeva |
| Privremeni parovi ključeva | Tajnost prema naprijed — prošla uparivanja ostaju sigurna |
| Vizualni broj za provjeru (SAS) | Napada čovjeka u sredini (MITM) tijekom razmjene ključeva |
| SHA-256 hash kao ključ dokumenta | Izdvajanja koda iz Firestorea |
| AES-256-GCM šifriranje | Prisluškivanja podataka signalizacije |
| Potvrda s obje strane | Jednostranog uparivanja bez znanja korisnika |
| DTLS-SRTP (WebRTC) | Prisluškivanja zvuka/videa |
Ti se slojevi nadopunjuju: ECDH štiti razmjenu ključeva, broj za provjeru štiti od MITM-a, AES-256-GCM štiti signalizaciju, a WebRTC štiti medijski sadržaj. Napadač bi morao probiti taj lanac na nekoliko mjesta, a da to uređaji ili roditelji ne primijete.