Blog

Bezbedno uparivanje u aplikaciji Baby Monitor Timmy

Kako ECDH, SAS broj i šifrovana signalizacija funkcionišu zajedno.

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

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:

  1. Zameni stvarne javne ključeve sačuvane u Firestore dokumentu svojim ključevima
  2. 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:

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:

  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 šifrovanje izvodi se kao SHA-256("enc:" + pairingKey) i predstavlja AES-256-GCM ključ za šifrovanu signalizaciju
  4. 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:

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.


Još članaka