Блог

Безбедно спарување во Baby Monitor Timmy

Како ECDH, SAS-бројот и шифрираната сигнализација функционираат заедно.

Пред Baby Monitor Timmy да пренесува аудио и видео, двата уреди треба да се пронајдат и да си веруваат. Овој чекор на спарување е најкритичниот момент во целиот процес. Тука објаснувам како Timmy ги спарува уредите, која криптографија стои зад тоа и зошто напаѓач во близина не може незабележано да ја преземе врската.

Проблемот: Како мојот уред знае со кого разговара?

Кога два уреди се поврзуваат првпат, главното прашање е: дали Уред А навистина разговара со Уред Б или некој се наоѓа меѓу нив? Во криптографијата, тоа се нарекува напад „човек во средина“ (MITM).

Timmy го решава ова со размена на клучеви Elliptic Curve Diffie-Hellman (ECDH) преку Firebase, во комбинација со визуелна потврда од корисникот.

Следниот дијаграм го прикажува целиот процес на спарување на прв поглед:

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
      

Целосна низа на протоколот за спарување — извор што може да се уредува: docs/diagrams/pairing-sequence.mmd

Чекор 1: Секој уред создава пар клучеви

При отворање на екранот за спарување, секој уред создава привремен ECDH пар клучеви на кривата P-256 (secp256r1):

Клучевите се создаваат со криптографски безбеден генератор на случајни броеви (Random.secure()) и се важечки само за овој единствен обид за спарување. За секој нов обид се создаваат нови клучеви.

Чекор 2: Размена на јавни клучеви преку Firebase

За да можат два уреди да се пронајдат, Timmy користи 4-знаковен код како место за средба. Овој код може автоматски да се открие преку Nearby Connections (Bluetooth Low Energy) или да се внесе рачно. Тој нема криптографска вредност; само им помага на двата уреди да го пронајдат истиот документ во Firebase Firestore.

Штом двата уреди го знаат кодот, секој го запишува својот јавен ECDH клуч во заеднички Firestore документ. Потоа секој уред го чита јавниот клуч на другиот уред од тој документ.

Најважно: се испраќа само јавниот клуч. Приватниот клуч никогаш не го напушта уредот. Секој што го следи сообраќајот на Firebase ги гледа јавните клучеви, но не може да ја пресмета заедничката тајна од нив. Тоа се потпира на тежината на проблемот на дискретен логаритам на елиптична крива (ECDLP).

Чекор 3: Пресметување на заедничката тајна

Штом двата уреди ќе ги откријат јавните клучеви еден на друг, независно ја пресметуваат истата заедничка тајна:

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

Математиката на елиптичните криви гарантира дека двете пресметки даваат ист резултат, иако секој уред го знае само својот приватен клуч и јавниот клуч на другиот.

Чекор 4: Бројот за потврда (SAS)

Од заедничката тајна се изведува кратка низа за автентикација (SAS) — двоцифрен број прикажан на двата уреди:

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

Двата уреди го прикажуваат истиот број — на пример, 42. Корисникот визуелно проверува дали броевите на двата екрана се совпаѓаат, па потврдува на секој уред поединечно.

Зошто напаѓач не може да го фалсификува ова

Напаѓач „човек во средина“ би морал да ја пресретне размената на клучеви во Firebase. Поточно, би морал да:

  1. Ги замени вистинските јавни клучеви зачувани во Firestore документот со свои
  2. Воспостави одделни заеднички тајни со секој уред
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)
      

Откривање напад „човек во средина“ преку несовпаѓање на SAS — извор што може да се уредува: docs/diagrams/mitm-detection.mmd

Во овој случај, напаѓачот пресметува заедничка тајна S_A со Уред А и различна заедничка тајна S_B со Уред Б. Бидејќи S_A ≠ S_B, уредите пресметуваат различни броеви за потврда.

Напаѓачот не може да направи броевите да се совпаѓаат бидејќи:

Корисникот гледа различни броеви на екраните и го откажува спарувањето. Во тој момент, нападот станува видлив.

Чекор 5: Завршување на спарувањето

Дури откако корисникот ќе ја потврди проверката на двата уреди се завршува спарувањето:

  1. Се изведува 64-знаковен клуч за спарување (256 бита) од заедничката тајна: SHA-256("pair:" + sharedSecret) → pairingKey
  2. Клучот за документот се изведува како SHA-256("doc:" + pairingKey) и служи како клуч за Firestore документот
  3. Клучот за шифрирање се изведува како SHA-256("enc:" + pairingKey) и го обезбедува AES-256-GCM клучот за шифрирана сигнализација
  4. Двата уреди го зачувуваат истиот клуч за спарување и преминуваат на избор на режим

Оттука натаму, сите следни обиди за поврзување (Firestore сигнализација, поставување WebRTC) се шифрирани со заедничкиот AES-256-GCM клуч. Клучот за спарување никогаш не се испраќа до серверската страна; само неговиот SHA-256 хеш се користи како идентификатор на документот.

Системска архитектура

Следниот дијаграм ги прикажува компонентите вклучени во спарувањето и комуникацијата:

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

Преглед на системската архитектура — извор што може да се уредува: docs/diagrams/pairing-architecture.mmd

Комуникациски патеки во детали:

Резервна опција: Рачно внесување код

Ако Bluetooth не е достапен (на пр., на постари уреди), 4-знаковниот код може да се внесе и рачно. Рачното внесување користи истата ECDH размена на клучеви и истата SAS-потврда како автоматското спарување. Единствената разлика е што кодот го чита и внесува корисникот, наместо да се открие преку BLE.

Бидејќи ECDH размената на клучеви во двата случаи се одвива преку Firebase, безбедноста е идентична. 4-знаковниот код е само место за средба; вистинското шифрирање се заснова на 256-битниот клуч изведен од ECDH.

Резиме

Безбедносен механизам Штити од
ECDH размена на клучеви (P-256) Прислушување на сообраќајот при размена на клучеви
Привремени парови клучеви Тајност нанапред — претходните спарувања остануваат безбедни
Визуелен број за потврда (SAS) Напад „човек во средина“ (MITM) при размена на клучеви
SHA-256 хеш како клуч за документ Извлекување на код од Firestore
AES-256-GCM шифрирање Прислушување на податоците за сигнализација
Потврда од двете страни Еднострано спарување без знаење на корисникот
DTLS-SRTP (WebRTC) Прислушување на аудио/видео

Овие слоеви работат заедно: ECDH ја штити размената на клучеви, бројот за потврда штити од MITM, AES-256-GCM ја штити сигнализацијата, а WebRTC ги штити медиумите. Напаѓачот би морал да го пробие овој синџир на неколку места, без уредите или родителите да забележат.


Повеќе статии