Pre nego što Baby Monitor Timmy prenese zvuk i video, dva uređaja moraju da pronađu jedan drugi i uspostave međusobno poverenje. Ovaj korak uparivanja je najvažniji trenutak u celom procesu. Ovde objašnjavam kako Timmy uparuje uređaje, koja se kriptografija koristi i zašto napadač u blizini ne može neprimetno da preuzme vezu.
Problem: Kako moj uređaj zna s kim razgovara?
Kada se dva uređaja povezuju prvi put, ključno pitanje je: da li Uređaj A zaista razgovara sa Uređajem B ili se neko nalazi između njih? U kriptografiji se to zove napad čoveka u sredini (MITM).
Timmy ovo rešava pomoću razmene ključeva Elliptic Curve Diffie-Hellman (ECDH) preko Firebase-a, u kombinaciji sa vizuelnom proverom korisnika.
Sledeći dijagram na prvi pogled prikazuje ceo tok uparivanja:
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
Potpuni redosled protokola za uparivanje — izvor koji može da se uređuje: docs/diagrams/pairing-sequence.mmd
Korak 1: Svaki uređaj generiše par ključeva
Pri otvaranju ekrana za uparivanje svaki uređaj generiše privremeni ECDH par ključeva na P-256 krivoj (secp256r1):
- Privatni ključ — ostaje isključivo na uređaju
- Javni ključ — razmenjuje se preko Firebase-a
Ključevi se kreiraju pomoću kriptografski bezbednog generatora slučajnih brojeva (Random.secure())
i važe samo za ovaj jedan pokušaj uparivanja. Novi ključevi se generišu
za svaki novi pokušaj.
Korak 2: Razmena javnih ključeva preko Firebase-a
Da bi se dva uređaja pronašla, Timmy koristi kod od 4 znaka kao mesto susreta. Ovaj kod može automatski da se otkrije putem Nearby Connections (Bluetooth Low Energy) ili da se unese ručno. On nema nikakvu kriptografsku vrednost; služi samo da oba uređaja pronađu isti Firebase Firestore dokument.
Kada oba uređaja znaju kod, svaki upisuje svoj javni ECDH ključ u zajednički Firestore dokument. Zatim svaki uređaj iz tog dokumenta čita javni ključ drugog uređaja.
Ključno: šalje se samo javni ključ. Privatni ključ nikada ne napušta uređaj. Svako ko prati Firebase saobraćaj vidi javne ključeve, ali ne može da izračuna zajedničku tajnu na osnovu njih. To se zasniva na težini problema diskretnog logaritma na eliptičkim krivama (ECDLP).
Korak 3: Izračunavanje zajedničke tajne
Kada oba uređaja otkriju javni ključ onog drugog, nezavisno izračunavaju istu zajedničku tajnu:
sharedSecret = ECDH(myPrivateKey, remotePublicKey)
→ 32 bytes (identical on both devices)
Matematika eliptičkih krivih garantuje da oba izračunavanja daju isti rezultat, iako svaki uređaj zna samo svoj privatni ključ i javni ključ drugog uređaja.
Korak 4: Broj za proveru (SAS)
Iz zajedničke tajne izvodi se kratak niz za autentifikaciju (SAS) — dvocifreni broj koji se prikazuje 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 primer, 42. Korisnik vizuelno proverava da li se brojevi na oba ekrana podudaraju, a zatim potvrđuje na svakom uređaju pojedinačno.
Zašto napadač ne može da falsifikuje ovo
Napadač čovek-u-sredini morao bi da presretne razmenu ključeva u Firebase-u. Konkretno, morao bi da:
- Zameni stvarne javne ključeve sačuvane u Firestore dokumentu svojim ključevima
- Uspostavi odvojene 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 čoveka-u-sredini putem nepodudaranja SAS-a — izvor koji može da se uređuje: docs/diagrams/mitm-detection.mmd
U ovom slučaju napadač izračunava zajedničku tajnu S_A sa Uređajem A i
drugačiju zajedničku tajnu S_B sa Uređajem B. Pošto S_A ≠ S_B,
uređaji izračunavaju različite brojeve za proveru.
Napadač ne može da učini da se brojevi podudaraju zato što:
- Ne zna privatne ključeve uređaja
- SHA-256 nije reverzibilan
- Verovatnoća nasumičnog podudaranja je samo 1 prema 100
Korisnik vidi različite brojeve na ekranima i otkazuje uparivanje. U tom trenutku napad postaje vidljiv.
Korak 5: Završavanje uparivanja
Tek nakon što korisnik potvrdi proveru na oba uređaja uparivanje se završ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 šifrovanje izvodi se kao
SHA-256("enc:" + pairingKey)i predstavlja AES-256-GCM ključ za šifrovanu signalizaciju - Oba uređaja čuvaju isti ključ za uparivanje i prelaze na izbor režima
Od ovog trenutka, svi naredni pokušaji povezivanja (Firestore signalizacija, WebRTC podešavanje) šifrovani su zajedničkim AES-256-GCM ključem. Ključ za uparivanje se nikada ne šalje pozadinskom sistemu; samo se njegov SHA-256 heš koristi kao identifikator dokumenta.
Arhitektura sistema
Sledeć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 sistema — izvor koji može da se uređuje: docs/diagrams/pairing-architecture.mmd
Putanje komunikacije detaljno:
- WebRTC peer-to-peer (debela linija): Zvuk, video i DataChannel idu direktno između uređaja — šifrovani DTLS-SRTP-om. Nijedan server ne vidi te podatke.
- Firebase Firestore (puna linija): Podaci za uparivanje (ECDH ključevi) i signalizacija (SDP/ICE) prolaze kroz Firestore — šifrovani s kraja na kraj pomoću AES-256-GCM. Firebase ne može da dešifruje podatke.
- STUN server: Oba uređaja otkrivaju svoju javnu IP adresu kako bi mogla da se uspostavi direktna peer-to-peer veza.
- TURN relej: Ako direktna veza nije moguća (npr. preko mobilnih podataka), izabrani lokalni ili Cloudflare TURN server prosleđuje šifrovani medijski sadržaj. Kratkotrajni pristupni podaci (24 h) preuzimaju se preko Firebase Cloud Functions.
- Bluetooth LE (isprekidana linija): Nearby Connections automatski otkriva uređaje u blizini — prenosi se samo kod za susret, bez materijala ključeva.
Rezervna opcija: ručni unos koda
Ako Bluetooth nije dostupan (npr. na starijim uređajima), kod od 4 znaka može da se unese i ručno. Ručni unos koristi istu ECDH razmenu ključeva i istu SAS proveru kao automatsko uparivanje. Jedina razlika je što korisnik čita i unosi kod, umesto da se on otkrije putem BLE-a.
Pošto se ECDH razmena ključeva u oba slučaja odvija preko Firebase-a, bezbednost je identična. Kod od 4 znaka je samo mesto susreta; pravo šifrovanje zasniva se na 256-bitnom ključu izvedenom iz ECDH-a.
Sažetak
| Bezbednosni mehanizam | Štiti od |
|---|---|
| ECDH razmena ključeva (P-256) | Prisluškivanja saobraćaja tokom razmene ključeva |
| Privremeni parovi ključeva | Poverljivost unapred — prethodna uparivanja ostaju bezbedna |
| Broj za vizuelnu proveru (SAS) | Čovek-u-sredini (MITM) tokom razmene ključeva |
| SHA-256 heš kao ključ dokumenta | Izvlačenja koda iz Firestore-a |
| AES-256-GCM šifrovanje | Prisluškivanja podataka signalizacije |
| Potvrda na oba uređaja | Jednostranog uparivanja bez znanja korisnika |
| DTLS-SRTP (WebRTC) | Prisluškivanja zvuka/videa |
Ovi slojevi rade zajedno: ECDH štiti razmenu ključeva, broj za proveru štiti od MITM-a, AES-256-GCM štiti signalizaciju, a WebRTC štiti medijski sadržaj. Napadač bi morao da probije ovaj lanac na više mesta, a da uređaji ili roditelji to ne primete.