Blogg

Örugg pörun í Baby Monitor Timmy

Hvernig ECDH, SAS-númerið og dulkóðaðar merkjasendingar vinna saman.

Áður en Baby Monitor Timmy sendir hljóð og mynd þurfa tvö tæki að finna hvort annað og treysta hvort öðru. Þetta pörunar skref er mikilvægasta augnablikið í öllu ferlinu. Hér útskýri ég hvernig Timmy parar tæki, hvaða dulmál býr að baki og hvers vegna árásaraðili í nágrenninu getur ekki tekið yfir tenginguna án þess að eftir því sé tekið.

Vandinn: Hvernig veit tækið mitt við hvern það er að tala?

Þegar tvö tæki tengjast í fyrsta sinn er meginspurningin: Talar tæki A í raun við tæki B, eða er einhver á milli þeirra? Í dulmálsfræði kallast það milliliðaárás (MITM).

Timmy leysir þetta með lyklaskiptum með Diffie-Hellman á sporgerli (ECDH) í gegnum Firebase, ásamt sjónrænni staðfestingu notandans.

Skýringarmyndin hér að neðan sýnir allt pörunarferlið í einu yfirliti:

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
      

Heildarröð pörunarsamskiptareglunnar — breytanleg frumskrá: docs/diagrams/pairing-sequence.mmd

Skref 1: Hvort tæki býr til lyklapar

Þegar pörunarskjárinn er opnaður býr hvort tæki til tímabundið ECDH-lyklapar á P-256-ferlinum (secp256r1):

Lyklarnir eru búnir til með dulmálslega öruggum slembitölugjafa (Random.secure()) og eru aðeins gildir í þessari einu pörunartilraun. Nýir lyklar eru búnir til fyrir hverja nýja tilraun.

Skref 2: Skipst á opinberum lyklum í gegnum Firebase

Til að tvö tæki geti fundið hvort annað notar Timmy 4-stafa kóða sem fundarstað. Þennan kóða er hægt að finna sjálfkrafa með Nearby Connections (Bluetooth Low Energy) eða slá hann inn handvirkt. Hann hefur ekkert dulmálslegt gildi; hann gerir tækjunum aðeins kleift að finna sama skjalið í Firebase Firestore.

Þegar bæði tækin þekkja kóðann skrifar hvort þeirra sinn opinbera ECDH-lykil í sameiginlegt Firestore-skjal. Síðan les hvort tæki opinbera lykil hins tækisins úr skjalinu.

Mikilvægt: aðeins opinberi lykillinn er sendur. Einkalykillinn fer aldrei úr tækinu. Sá sem fylgist með Firebase-umferð sér opinbera lykla en getur ekki reiknað út sameiginlega leyndarmálið út frá þeim. Það byggir á erfiðleikanum við að reikna strjála lógaritma á sporgerlum (ECDLP).

Skref 3: Reikna sameiginlega leyndarmálið

Þegar bæði tækin hafa fundið opinbera lykil hvors annars reikna þau hvort um sig út sama sameiginlega leyndarmál:

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

Stærðfræði sporöskjulaga ferla tryggir að báðir útreikningar gefi sömu niðurstöðu, þótt hvort tæki þekki aðeins sinn einkalykil og opinbera lykil hins.

Skref 4: Staðfestingarnúmerið (SAS)

Úr sameiginlega leyndarmálinu er stuttur auðkenningarstrengur (SAS) leiddur — tveggja tölustafa tala sem birtist á báðum tækjum:

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

Bæði tækin sýna sama númer — til dæmis 42. Notandinn ber sjónrænt saman hvort númerin á báðum skjám séu eins og staðfestir síðan á hvoru tæki fyrir sig.

Hvers vegna árásaraðili getur ekki falsað þetta

Árásaraðili í miðjunni þyrfti að grípa inn í lyklaskiptin í Firebase. Nánar tiltekið þyrfti hann að:

  1. Skipta út raunverulegu opinberu lyklunum í Firestore-skjalinu fyrir sína eigin
  2. Koma á aðskildum sameiginlegum leyndarmálum með hvoru tæki
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)
      

Greining milliliðaárásar með ósamræmi í SAS — breytanleg frumskrá: docs/diagrams/mitm-detection.mmd

Í því tilviki reiknar árásaraðilinn sameiginlegt leyndarmál S_A með tæki A og annað sameiginlegt leyndarmál S_B með tæki B. Þar sem S_A ≠ S_B, reikna tækin út ólík staðfestingarnúmer.

Árásaraðilinn getur ekki látið númerin passa vegna þess að:

Notandinn sér ólík númer á skjánum á hvoru tæki og hættir við pörunina. Þá er árásin orðin sýnileg.

Skref 5: Ljúka pöruninni

Aðeins eftir að notandinn hefur staðfest að númerin passi á báðum tækjum lýkur pöruninni:

  1. Einn 64-stafa pörunarlykill (256 bitar) er leiddur af sameiginlega leyndarmálinu: SHA-256("pair:" + sharedSecret) → pairingKey
  2. Skjalslykillinn er leiddur sem SHA-256("doc:" + pairingKey) og er notaður sem Firestore-skjalalykill
  3. Dulkóðunarlykillinn er leiddur sem SHA-256("enc:" + pairingKey) og gefur AES-256-GCM-lykilinn fyrir dulkóðaðar merkjasendingar
  4. Bæði tækin geyma sama pörunarlykil og fara í val á ham

Frá þessum tímapunkti eru allar frekari tengingartilraunir (Firestore-merkjasendingar, WebRTC-uppsetning) dulkóðaðar með sameiginlega AES-256-GCM-lyklinum. Pörunarlykillinn er aldrei sendur í bakendann; aðeins SHA-256-tætigildi hans er notað sem auðkenni skjalsins.

Kerfisarkitektúr

Skýringarmyndin hér að neðan sýnir þá hluta sem koma að pörun og samskiptum:

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

Yfirlit yfir kerfisarkitektúr — breytanleg frumskrá: docs/diagrams/pairing-architecture.mmd

Samskiptaleiðir í smáatriðum:

Valkostur: Kóði sleginn inn handvirkt

Ef Bluetooth er ekki tiltækt (t.d. í eldri tækjum) er einnig hægt að slá 4-stafa kóðann inn handvirkt. Handvirk innsláttur kóðans notar sömu ECDH-lyklaskipti og sömu SAS-staðfestingu og sjálfvirk pörun. Eini munurinn er að notandinn les og slær inn kóðann í stað þess að hann finnist með BLE.

Þar sem ECDH-lyklaskiptin fara í gegnum Firebase í báðum tilvikum er öryggið eins. 4-stafa kóðinn er aðeins fundarstaður; raunveruleg dulkóðun byggir á 256-bita lykli sem er leiddur af ECDH.

Samantekt

Öryggisráðstöfun Verndar gegn
ECDH-lyklaskipti (P-256) Hlerun á umferð lyklaskipta
Tímabundin lyklapör Framvirk leynd — fyrri paranir eru áfram öruggar
Sjónrænt staðfestingarnúmer (SAS) Milliliðaárás (MITM) við lyklaskipti
SHA-256-tætigildi sem skjalslykill Útdráttur kóða úr Firestore
AES-256-GCM-dulkóðun Hlerun á merkjasendingargögnum
Staðfesting beggja aðila Einhliða pörun án vitundar notandans
DTLS-SRTP (WebRTC) Hlerun á hljóði og myndskeiðum

Þessi lög vinna saman: ECDH verndar lyklaskiptin, staðfestingarnúmerið verndar gegn MITM, AES-256-GCM verndar merkjasendingar og WebRTC verndar miðla. Árásaraðili þyrfti að rjúfa þessa keðju á mörgum stöðum án þess að tækin eða foreldrarnir tækju eftir því.


Fleiri greinar