Emuārs

Droša savienošana pārī lietotnē Baby Monitor Timmy

Kā kopā darbojas ECDH, SAS numurs un šifrēta signalizācija.

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

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:

  1. Aizstāt Firestore dokumentā saglabātās īstās publiskās atslēgas ar savām
  2. 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:

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:

  1. Tiek iegūta 64 rakstzīmju savienošanas pārī atslēga (256 biti) no kopīgā noslēpuma: SHA-256("pair:" + sharedSecret) → pairingKey
  2. Dokumenta atslēga tiek iegūta kā SHA-256("doc:" + pairingKey) un tiek izmantota kā Firestore dokumenta atslēga
  3. Šifrēšanas atslēga tiek iegūta kā SHA-256("enc:" + pairingKey) un nodrošina AES-256-GCM atslēgu šifrētai signalizācijai
  4. 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:

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.


Vairāk rakstu