Voordat Baby Monitor Timmy klank en video oordra, moet twee toestelle mekaar vind en mekaar vertrou. Hierdie koppeling stap is die belangrikste oomblik in die hele proses. Hier verduidelik ek hoe Timmy koppel, watter kriptografie daaragter sit en hoekom ’n aanvaller naby nie ongemerk die verbinding kan oorneem nie.
Die probleem: Hoe weet my toestel met wie dit praat?
Wanneer twee toestelle vir die eerste keer verbind, is die sentrale vraag: praat Toestel A regtig met Toestel B, of sit iemand tussenin? In kriptografie word dit ’n man-in-the-middle-aanval (MITM) genoem.
Timmy los dit op met ’n Elliptic Curve Diffie-Hellman (ECDH)-sleuteluitruiling oor Firebase, saam met visuele verifikasie deur die gebruiker.
Die volgende diagram wys die volledige koppelingsproses in ’n oogopslag:
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
Volledige volgorde van die koppelingsprotokol — wysigbare bron: docs/diagrams/pairing-sequence.mmd
Stap 1: Elke toestel skep ’n sleutelpaar
Wanneer die koppelskerm oopmaak, skep elke toestel ’n tydelike ECDH-sleutelpaar op die P-256-kurwe (secp256r1):
- ’n privaat sleutel — bly uitsluitlik op die toestel
- ’n openbare sleutel — word oor Firebase uitgeruil
Die sleutels word geskep met ’n kriptografies veilige ewekansigegetalgenerator (Random.secure())
en is slegs geldig vir hierdie enkele koppelingspoging. Vars sleutels word
vir elke nuwe poging geskep.
Stap 2: Ruil openbare sleutels oor Firebase uit
Om twee toestelle mekaar te laat vind, gebruik Timmy ’n 4-karakter-kode as ’n ontmoetingspunt. Hierdie kode kan outomaties ontdek word via Nearby Connections (Bluetooth Low Energy) of met die hand ingevoer word. Dit het geen kriptografiese waarde nie; dit laat net albei toestelle dieselfde Firebase Firestore-dokument vind.
Sodra albei toestelle die kode ken, skryf elkeen sy openbare ECDH-sleutel na ’n gedeelde Firestore-dokument. Daarna lees elke toestel die ander toestel se openbare sleutel uit daardie dokument.
Belangrik: slegs die openbare sleutel word gestuur. Die privaat sleutel verlaat nooit die toestel nie. Enigiemand wat Firebase-verkeer dophou, sien openbare sleutels, maar kan nie die gedeelde geheim bereken nie daaruit. Dit berus op die moeilikheid van die Elliptic Curve Discrete Logarithm Problem (ECDLP).
Stap 3: Bereken die gedeelde geheim
Sodra albei toestelle mekaar se openbare sleutel ontdek het, bereken hulle onafhanklik dieselfde gedeelde geheim:
sharedSecret = ECDH(myPrivateKey, remotePublicKey)
→ 32 bytes (identical on both devices)
Die wiskunde van elliptiese kurwes waarborg dat albei berekeninge dieselfde resultaat gee, al ken elke toestel net sy eie privaat sleutel en die ander een se openbare sleutel.
Stap 4: Die verifikasienommer (SAS)
Uit die gedeelde geheim word ’n Short Authentication String (SAS) afgelei — ’n tweesyfernommer wat op albei toestelle vertoon word:
hash = SHA-256("sas:" + sort(pubkeyA, pubkeyB) + sharedSecret)
number = (hash[0] × 256 + hash[1]) mod 100 → 00 to 99
Albei toestelle wys dieselfde nommer — byvoorbeeld 42. Die gebruiker vergelyk visueel of die nommers op albei skerms ooreenstem en bevestig dan op elke toestel afsonderlik.
Hoekom ’n aanvaller dit nie kan namaak nie
’n Man-in-the-middle sou die sleuteluitruiling in Firebase moes onderskep. Spesifiek, sou hulle moes:
- Die regte openbare sleutels in die Firestore-dokument met hul eie vervang
- Afsonderlike gedeelde geheime met elke toestel vestig
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)
Opsporing van man-in-the-middle via ’n SAS-wanpassing — wysigbare bron: docs/diagrams/mitm-detection.mmd
In hierdie geval bereken die aanvaller ’n gedeelde geheim S_A met Toestel A en ’n
ander gedeelde geheim S_B met Toestel B. Aangesien S_A ≠ S_B,
bereken die toestelle verskillende verifikasienommers.
Die aanvaller kan nie die nommers laat ooreenstem nie, want:
- Hulle ken nie die toestelle se privaat sleutels nie
- SHA-256 kan nie omgekeer word nie
- Die kans op ’n toevallige passing is net 1 uit 100
Die gebruiker sien verskillende nommers op die skerms en kanselleer die koppeling. Op daardie punt het die aanval sigbaar geword.
Stap 5: Voltooi die koppeling
Eers nadat die gebruiker verifikasie op albei toestelle bevestig het, word die koppeling voltooi:
- ’n 64-karakter-koppelingsleutel (256 bisse) word uit die gedeelde geheim afgelei:
SHA-256("pair:" + sharedSecret) → pairingKey - Die dokumentsleutel word afgelei as
SHA-256("doc:" + pairingKey)en dien as die Firestore-dokumentsleutel - Die enkripsiesleutel word afgelei as
SHA-256("enc:" + pairingKey)en verskaf die AES-256-GCM-sleutel vir geënkripteerde seinboodskappe - Albei toestelle stoor dieselfde koppelingsleutel en gaan na moduskeuse
Van hierdie punt af word alle verdere verbindingspogings (Firestore-seinboodskappe, WebRTC-opstelling) met die gedeelde AES-256-GCM-sleutel geënkripteer. Die koppelingsleutel word nooit na die backend gestuur nie; slegs die SHA-256-hutswaarde daarvan word as die dokumentidentifiseerder gebruik.
Stelselargitektuur
Die volgende diagram wys die komponente wat by koppeling en kommunikasie betrokke is:
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
Oorsig van die stelselargitektuur — wysigbare bron: docs/diagrams/pairing-architecture.mmd
Kommunikasiepaaie in detail:
- WebRTC eweknie-tot-eweknie (dik lyn): Klank, video en DataChannel vloei direk tussen toestelle — geënkripteer met DTLS-SRTP. Geen bediener sien hierdie data nie.
- Firebase Firestore (soliede lyn): Koppelingsdata (ECDH-sleutels) en seinboodskappe (SDP/ICE) gaan deur Firestore — end-tot-end geënkripteer met AES-256-GCM. Firebase kan nie die data dekripteer nie.
- STUN-bediener: Albei toestelle ontdek hul openbare IP-adres sodat ’n direkte eweknie-tot-eweknie-verbinding opgestel kan word.
- TURN-herleier: As ’n direkte verbinding nie moontlik is nie (bv. op mobiele data), herlei die gekose plaaslike of Cloudflare TURN-bediener die geënkripteerde media. Kortstondige geloofsbriewe (24 h) word via Firebase Cloud Functions verkry.
- Bluetooth LE (stippellyn): Nearby Connections ontdek nabygeleë toestelle outomaties — slegs die ontmoetingskode word oorgedra, geen sleutelmateriaal nie.
Terugval: Handmatige kode-invoer
As Bluetooth nie beskikbaar is nie (bv. op ouer toestelle), kan die 4-karakter-kode ook met die hand ingetik word. Handmatige invoer gebruik dieselfde ECDH-sleuteluitruiling en dieselfde SAS-verifikasie as outomatiese koppeling. Die enigste verskil is dat die gebruiker die kode lees en intik, eerder as dat dit deur BLE ontdek word.
Omdat die ECDH-sleuteluitruiling in albei gevalle oor Firebase plaasvind, is die sekuriteit identies. Die 4-karakter-kode is net ’n ontmoetingspunt; die werklike enkripsie berus op die 256-bis-sleutel wat uit ECDH afgelei word.
Opsomming
| Sekuriteitsmeganisme | Beskerm teen |
|---|---|
| ECDH-sleuteluitruiling (P-256) | Afluister van sleuteluitruilverkeer |
| Tydelike sleutelpare | Voorwaartse geheimhouding — vorige koppelings bly veilig |
| Visuele verifikasienommer (SAS) | Man-in-the-middle (MITM) tydens sleuteluitruiling |
| SHA-256-hutswaarde as dokumentsleutel | Kode-onttrekking uit Firestore |
| AES-256-GCM-enkripsie | Afluister van seinboodskapdata |
| Bevestiging aan albei kante | Eensydige koppeling sonder die gebruiker se medewete |
| DTLS-SRTP (WebRTC) | Afluister van klank/video |
Hierdie lae pas saam: ECDH beskerm die sleuteluitruiling, die verifikasienommer beskerm teen MITM, AES-256-GCM beskerm seinboodskappe en WebRTC beskerm media. ’n Aanvaller sou hierdie ketting op verskeie plekke moes breek sonder dat die toestelle of ouers dit agterkom.