Pirms Baby Monitor Timmy pārraida audio un video, abām ierīcēm ir jāatrod vienai otra un jāuzticas vienai otrai. Šis savienošanas pārī solis ir vissvarīgākais brīdis visā procesā. Šeit skaidroju, kā Timmy savieno ierīces pārī, kāda kriptogrāfija tam ir pamatā un kāpēc tuvumā esošs uzbrucējs nevar nepamanīti pārņemt savienojumu.
Problēma: kā mana ierīce zina, ar ko tā sazinās?
Kad divas ierīces savienojas pirmo reizi, galvenais jautājums ir: vai ierīce A tiešām sazinās ar ierīci B, vai arī kāds ir pa vidu? Kriptogrāfijā to sauc par starpnieka uzbrukumu (MITM).
Timmy to atrisina ar eliptisko līkņu Difija–Helmana (ECDH) atslēgu apmaiņu izmantojot Firebase, kopā ar lietotāja veiktu vizuālu pārbaudi..
Šajā diagrammā redzams viss savienošanas pārī process vienā skatā:
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
Pilna savienošanas pārī protokola secība — rediģējams avots: docs/diagrams/pairing-sequence.mmd
1. solis: katra ierīce ģenerē atslēgu pāri
Atverot savienošanas pārī ekrānu, katra ierīce ģenerē pagaidu ECDH atslēgu pāri uz P-256 līknes (secp256r1):
- Privātā atslēga — paliek tikai ierīcē
- Publiskā atslēga — tiek apmainīta, izmantojot Firebase
Atslēgas tiek izveidotas ar kriptogrāfiski drošu nejaušo skaitļu ģeneratoru (Random.secure())
un ir derīgas tikai šim vienīgajam savienošanas pārī mēģinājumam. Katram jaunam mēģinājumam tiek ģenerētas jaunas atslēgas.
2. solis: publisko atslēgu apmaiņa, izmantojot Firebase
Lai abas ierīces varētu atrast viena otru, Timmy izmanto 4 rakstzīmju kodu kā satikšanās punktu. Šo kodu var automātiski atrast, izmantojot Nearby Connections (Bluetooth Low Energy), vai ievadīt manuāli. Tam nav kriptogrāfiskas vērtības; tas tikai palīdz abām ierīcēm atrast vienu un to pašu Firebase Firestore dokumentu.
Kad abas ierīces zina kodu, katra ieraksta savu publisko ECDH atslēgu kopīgā Firestore dokumentā. Pēc tam katra ierīce no šī dokumenta nolasa otras ierīces publisko atslēgu.
Svarīgi: tiek nosūtīta tikai publiskā atslēga. Privātā atslēga nekad nepamet ierīci. Ikviens, kas vēro Firebase datplūsmu, redz publiskās atslēgas, bet nevar aprēķināt kopīgo noslēpumu no tām. Tas balstās uz eliptisko līkņu diskrētā logaritma problēmas (ECDLP) sarežģītību.
3. solis: kopīgā noslēpuma aprēķināšana
Kad abas ierīces ir atradušas viena otras publisko atslēgu, tās neatkarīgi aprēķina vienu un to pašu kopīgo noslēpumu:
sharedSecret = ECDH(myPrivateKey, remotePublicKey)
→ 32 bytes (identical on both devices)
Eliptisko līkņu matemātika garantē, ka abi aprēķini dod vienādu rezultātu, lai gan katra ierīce zina tikai savu privāto atslēgu un otras ierīces publisko atslēgu.
4. solis: pārbaudes numurs (SAS)
No kopīgā noslēpuma tiek iegūta īsa autentifikācijas virkne (SAS) — divciparu numurs, kas tiek parādīts abās ierīcēs:
hash = SHA-256("sas:" + sort(pubkeyA, pubkeyB) + sharedSecret)
number = (hash[0] × 256 + hash[1]) mod 100 → 00 to 99
Abās ierīcēs tiek parādīts viens un tas pats numurs — piemēram, 42. Lietotājs vizuāli salīdzina, vai numuri abos ekrānos sakrīt, un pēc tam apstiprina tos katrā atsevišķā ierīcē.
Kāpēc uzbrucējs to nevar viltot
Starpnieka uzbrucējam būtu jāpārtver atslēgu apmaiņa Firebase. Konkrēti, viņam būtu nepieciešams:
- Aizstāt Firestore dokumentā saglabātās īstās publiskās atslēgas ar savām
- Izveidot atsevišķus kopīgos noslēpumus ar katru ierīci
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)
Starpnieka uzbrukuma noteikšana pēc SAS neatbilstības — rediģējams avots: docs/diagrams/mitm-detection.mmd
Šajā gadījumā uzbrucējs aprēķina kopīgo noslēpumu S_A ar ierīci A un citu
kopīgo noslēpumu S_B ar ierīci B. Tā kā S_A ≠ S_B,
ierīces aprēķina atšķirīgus pārbaudes numurus.
Uzbrucējs nevar panākt, lai numuri sakristu, jo:
- Viņš nezina ierīču privātās atslēgas
- SHA-256 nav atgriezenisks
- Nejaušas sakritības varbūtība ir tikai 1 no 100
Lietotājs ekrānos redz atšķirīgus numurus un atceļ savienošanu pārī. Tajā brīdī uzbrukums ir kļuvis redzams.
5. solis: savienošanas pārī pabeigšana
Tikai pēc tam, kad lietotājs ir apstiprinājis pārbaudi abās ierīcēs savienošana pārī tiek pabeigta:
- Tiek iegūta 64 rakstzīmju savienošanas pārī atslēga (256 biti) no kopīgā noslēpuma:
SHA-256("pair:" + sharedSecret) → pairingKey - Dokumenta atslēga tiek iegūta kā
SHA-256("doc:" + pairingKey)un tiek izmantota kā Firestore dokumenta atslēga - Šifrēšanas atslēga tiek iegūta kā
SHA-256("enc:" + pairingKey)un nodrošina AES-256-GCM atslēgu šifrētai signalizācijai - Abas ierīces saglabā vienu un to pašu savienošanas pārī atslēgu un pāriet uz režīma izvēli
Turpmāk visi savienojuma mēģinājumi (Firestore signalizācija, WebRTC iestatīšana) tiek šifrēti ar kopīgo AES-256-GCM atslēgu. Savienošanas pārī atslēga nekad netiek nosūtīta uz aizmugursistēmu; kā dokumenta identifikators tiek izmantota tikai tās SHA-256 jaucējvērtība.
Sistēmas arhitektūra
Šajā diagrammā redzamas savienošanā pārī un saziņā iesaistītās sastāvdaļas:
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
Sistēmas arhitektūras pārskats — rediģējams avots: docs/diagrams/pairing-architecture.mmd
Saziņas ceļi detalizēti:
- WebRTC vienādranga savienojums (bieza līnija): audio, video un DataChannel plūst tieši starp ierīcēm — šifrēti ar DTLS-SRTP. Neviens serveris šos datus neredz.
- Firebase Firestore (nepārtraukta līnija): savienošanas pārī dati (ECDH atslēgas) un signalizācija (SDP/ICE) tiek sūtīti caur Firestore — pilnībā šifrēti ar AES-256-GCM. Firebase nevar šos datus atšifrēt.
- STUN serveris: abas ierīces nosaka savu publisko IP adresi, lai varētu izveidot tiešu vienādranga savienojumu.
- TURN retranslators: ja tiešs savienojums nav iespējams (piemēram, izmantojot mobilos datus), izvēlētais vietējais vai Cloudflare TURN serveris retranslē šifrēto multividi. Īslaicīgas piekļuves tiesības (24 h) tiek iegūtas ar Firebase Cloud Functions palīdzību.
- Bluetooth LE (punktēta līnija): Nearby Connections automātiski atrod tuvumā esošās ierīces — tiek pārsūtīts tikai satikšanās kods, nevis atslēgu materiāls.
Rezerves iespēja: manuāla koda ievadīšana
Ja Bluetooth nav pieejams (piemēram, vecākās ierīcēs), 4 rakstzīmju kodu var ievadīt arī manuāli. Manuālā ievadīšanā tiek izmantota tā pati ECDH atslēgu apmaiņa un tā pati SAS pārbaude kā automātiskajā savienošanā pārī. Vienīgā atšķirība ir tāda, ka kodu lietotājs nolasa un ievada, nevis tas tiek atrasts ar BLE palīdzību.
Tā kā ECDH atslēgu apmaiņa abos gadījumos notiek, izmantojot Firebase, drošība ir identiska. 4 rakstzīmju kods ir tikai satikšanās punkts; īstā šifrēšana balstās uz no ECDH iegūto 256 bitu atslēgu.
Kopsavilkums
| Drošības mehānisms | Aizsargā pret |
|---|---|
| ECDH atslēgu apmaiņa (P-256) | Atslēgu apmaiņas datplūsmas noklausīšanos |
| Pagaidu atslēgu pāri | Tiešo slepenību — iepriekšējās savienošanas pārī reizes paliek drošas |
| Vizuālais pārbaudes numurs (SAS) | Starpnieka uzbrukumu (MITM) atslēgu apmaiņas laikā |
| SHA-256 jaucējvērtība kā dokumenta atslēga | Koda iegūšanu no Firestore |
| AES-256-GCM šifrēšana | Signalizācijas datu noklausīšanos |
| Apstiprināšana abās pusēs | Vienpusēju savienošanu pārī bez lietotāja ziņas |
| DTLS-SRTP (WebRTC) | Audio/video noklausīšanos |
Šie slāņi papildina cits citu: ECDH aizsargā atslēgu apmaiņu, pārbaudes numurs aizsargā pret MITM, AES-256-GCM aizsargā signalizāciju, bet WebRTC — multividi. Uzbrucējam būtu jāpārrauj šī ķēde vairākās vietās, ierīcēm vai vecākiem to nepamanot.