Blog

Varno seznanjanje v aplikaciji Baby Monitor Timmy

Kako skupaj delujejo ECDH, številka SAS in šifrirana signalizacija.

Preden Baby Monitor Timmy prenaša zvok in video, se morata napravi najti in si zaupati. Ta korak seznanjanja je najbolj kritičen trenutek celotnega postopka. Tukaj pojasnjujem, kako Timmy seznani napravi, katera kriptografija je v ozadju in zakaj bližnji napadalec ne more neopazno prevzeti povezave.

Težava: Kako moja naprava ve, s kom se pogovarja?

Ko se dve napravi prvič povežeta, je osrednje vprašanje: ali se naprava A res pogovarja z napravo B ali je nekdo vmes? V kriptografiji se temu reče napad s posrednikom (MITM).

Timmy to rešuje z izmenjavo ključev Diffie-Hellman na eliptičnih krivuljah (ECDH) prek storitve Firebase v kombinaciji z uporabnikovim vizualnim preverjanjem..

Naslednji diagram na kratko prikazuje celoten postopek seznanjanja:

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
      

Celotno zaporedje protokola seznanjanja — vir, ki ga lahko urejate: docs/diagrams/pairing-sequence.mmd

1. korak: Vsaka naprava ustvari par ključev

Ob odprtju zaslona za seznanjanje vsaka naprava ustvari začasni par ključev ECDH na krivulji P-256 (secp256r1):

Ključi so ustvarjeni z generatorjem kriptografsko varnih naključnih števil (Random.secure()) in so veljavni samo za ta poskus seznanjanja. Za vsak nov poskus se ustvarijo novi ključi.

2. korak: Izmenjava javnih ključev prek storitve Firebase

Da se napravi lahko najdeta, Timmy uporablja 4-znakovno kodo kot skupno točko. To kodo je mogoče samodejno odkriti prek Nearby Connections (Bluetooth Low Energy) ali vnesti ročno. Nima nobene kriptografske vrednosti; napravi le usmeri k istemu dokumentu v storitvi Firebase Firestore.

Ko obe napravi poznata kodo, vsaka zapiše svoj javni ključ ECDH v skupni dokument Firestore. Nato vsaka naprava iz tega dokumenta prebere javni ključ druge naprave.

Ključno je: pošlje se samo javni ključ. Zasebni ključ nikoli ne zapusti naprave. Kdor spremlja promet Firebase, vidi javne ključe, vendar iz njih ne more izračunati skupne skrivnosti. To temelji na zahtevnosti problema diskretnega logaritma na eliptičnih krivuljah (ECDLP).

3. korak: Izračun skupne skrivnosti

Ko obe napravi odkrijeta javni ključ druga druge, neodvisno izračunata isto skupno skrivnost:

sharedSecret = ECDH(myPrivateKey, remotePublicKey)
             → 32 bytes (identical on both devices)

Matematika eliptičnih krivulj zagotavlja, da oba izračuna dasta enak rezultat, čeprav vsaka naprava pozna le svoj zasebni ključ in javni ključ druge naprave.

4. korak: Preverjevalna številka (SAS)

Iz skupne skrivnosti se izpelje kratki niz za preverjanje pristnosti (SAS) — dvomestna številka, prikazana na obeh napravah:

hash   = SHA-256("sas:" + sort(pubkeyA, pubkeyB) + sharedSecret)
number = (hash[0] × 256 + hash[1]) mod 100   → 00 to 99

Obe napravi prikažeta isto številko — na primer 42. Uporabnik vizualno preveri, ali se številki na obeh zaslonih ujemata, nato pa potrdi na vsaki napravi posebej.

Zakaj napadalec tega ne more ponarediti

Napadalec, ki izvaja napad s posrednikom, bi moral prestreči izmenjavo ključev v storitvi Firebase. Natančneje, moral bi:

  1. Zamenjati prava javna ključa, shranjena v dokumentu Firestore, s svojima
  2. Z vsako napravo vzpostaviti ločeno skupno skrivnost
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)
      

Zaznavanje napada s posrednikom prek neujemanja SAS — vir, ki ga lahko urejate: docs/diagrams/mitm-detection.mmd

V tem primeru napadalec izračuna skupno skrivnost S_A z napravo A in drugo skupno skrivnost S_B z napravo B. Ker S_A ≠ S_B, napravi izračunata različni preverjevalni številki.

Napadalec ne more doseči, da bi se številki ujemali, ker:

Uporabnik na zaslonih vidi različni številki in prekine povezovanje. Napad je takrat postal viden.

5. korak: Dokončanje seznanjanja

Šele ko uporabnik potrdi preverjanje na obeh napravah se seznanjanje dokonča:

  1. Ključ za seznanjanje, dolg 64 znakov (256 bitov), se izpelje iz skupne skrivnosti: SHA-256("pair:" + sharedSecret) → pairingKey
  2. Ključ dokumenta je izpeljan kot SHA-256("doc:" + pairingKey) in se uporablja kot ključ dokumenta Firestore
  3. Šifrirni ključ je izpeljan kot SHA-256("enc:" + pairingKey) in zagotavlja ključ AES-256-GCM za šifrirano signalizacijo
  4. Obe napravi shranita isti ključ za seznanjanje in preideta na izbiro načina

Od tega trenutka so vsi nadaljnji poskusi povezave (signalizacija prek storitve Firestore in nastavitev WebRTC) šifrirani s skupnim ključem AES-256-GCM. Ključ za seznanjanje se nikoli ne pošlje v zaledni sistem; kot identifikator dokumenta se uporablja le njegova zgoščena vrednost SHA-256.

Arhitektura sistema

Naslednji diagram prikazuje komponente, vključene v seznanjanje in komunikacijo:

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 — vir, ki ga lahko urejate: docs/diagrams/pairing-architecture.mmd

Komunikacijske poti podrobneje:

Rezervna možnost: ročni vnos kode

Če Bluetooth ni na voljo (npr. na starejših napravah), lahko 4-znakovno kodo vnesete tudi ročno. Ročni vnos uporablja isto izmenjavo ključev ECDH in isto preverjanje SAS kot samodejno seznanjanje. Edina razlika je, da uporabnik kodo prebere in vnese, namesto da bi jo naprava odkrila prek BLE.

Ker izmenjava ključev ECDH v obeh primerih poteka prek storitve Firebase, je varnost enaka. 4-znakovna koda je le skupna točka; dejansko šifriranje temelji na 256-bitnem ključu, izpeljanem iz ECDH.

Povzetek

Varnostni mehanizem Ščiti pred
Izmenjava ključev ECDH (P-256) Prisluškovanjem prometu pri izmenjavi ključev
Začasni pari ključev Tajnost za naprej — pretekla seznanjanja ostanejo varna
Vizualna preverjevalna številka (SAS) Napadom s posrednikom (MITM) med izmenjavo ključev
Zgoščena vrednost SHA-256 kot ključ dokumenta Pridobivanjem kode iz storitve Firestore
Šifriranje AES-256-GCM Prisluškovanjem podatkom signalizacije
Potrditev na obeh straneh Enostranskemu seznanjanju brez vednosti uporabnika
DTLS-SRTP (WebRTC) Prisluškovanjem zvoku/videu

Te plasti se dopolnjujejo: ECDH ščiti izmenjavo ključev, preverjevalna številka ščiti pred MITM, AES-256-GCM ščiti signalizacijo, WebRTC pa medije. Napadalec bi moral to verigo prebiti na več mestih, ne da bi naprave ali starši to opazili.


Več člankov