Blog

Sigurno uparivanje u aplikaciji Baby Monitor Timmy

Kako ECDH, SAS broj i šifrirana signalizacija rade zajedno.

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):

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:

  1. Zamijeniti stvarne javne ključeve pohranjene u Firestore dokumentu vlastitima
  2. 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:

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:

  1. Ključ za uparivanje od 64 znaka (256 bita) izvodi se iz zajedničke tajne: SHA-256("pair:" + sharedSecret) → pairingKey
  2. Ključ dokumenta izvodi se kao SHA-256("doc:" + pairingKey) i služi kao ključ Firestore dokumenta
  3. Ključ za šifriranje izvodi se kao SHA-256("enc:" + pairingKey) i pruža AES-256-GCM ključ za šifriranu signalizaciju
  4. 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:

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.


Više članaka