Blog

Pemadanan Selamat pada Baby Monitor Timmy

Cara ECDH, nombor SAS dan pensinyalan disulitkan berfungsi bersama.

Sebelum Baby Monitor Timmy menghantar audio dan video, dua peranti perlu mencari satu sama lain dan saling mempercayai. Langkah pemadanan ini ialah saat paling penting dalam keseluruhan proses. Di sini saya menerangkan cara Timmy melakukan pemadanan, kriptografi yang digunakan dan sebab penyerang berdekatan tidak dapat mengambil alih sambungan tanpa disedari.

Masalahnya: Bagaimana peranti saya tahu dengan siapa ia sedang berhubung?

Apabila dua peranti bersambung buat kali pertama, persoalan utamanya ialah: adakah Peranti A benar-benar bercakap dengan Peranti B, atau ada seseorang di tengah-tengah? Dalam kriptografi, ini dipanggil serangan man-in-the-middle (MITM).

Timmy menyelesaikannya dengan pertukaran kunci Elliptic Curve Diffie-Hellman (ECDH) melalui Firebase, digabungkan dengan pengesahan visual oleh pengguna.

Rajah berikut menunjukkan keseluruhan aliran pemadanan sepintas lalu:

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
      

Urutan lengkap protokol pemadanan — sumber boleh disunting: docs/diagrams/pairing-sequence.mmd

Langkah 1: Setiap peranti menjana pasangan kunci

Apabila skrin pemadanan dibuka, setiap peranti menjana pasangan kunci ECDH sementara pada lengkung P-256 (secp256r1):

Kunci dijana menggunakan penjana nombor rawak yang selamat secara kriptografi (Random.secure()) dan hanya sah untuk percubaan pemadanan ini. Kunci baharu dijana bagi setiap percubaan pemadanan baharu.

Langkah 2: Menukar kunci awam melalui Firebase

Untuk membolehkan dua peranti mencari satu sama lain, Timmy menggunakan kod 4 aksara sebagai titik pertemuan. Kod ini boleh dikesan secara automatik melalui Nearby Connections (Bluetooth Low Energy) atau dimasukkan secara manual. Ia tiada nilai kriptografi; ia hanya membantu kedua-dua peranti menemui dokumen Firebase Firestore yang sama.

Setelah kedua-dua peranti mengetahui kod tersebut, setiap satu menulis kunci ECDH awamnya ke dalam dokumen Firestoreyang dikongsi. Kemudian setiap peranti membaca kunci awam peranti lain daripada dokumen itu.

Yang penting: hanya kunci awam dihantar. Kunci peribadi tidak pernah meninggalkan peranti. Sesiapa yang memantau trafik Firebase boleh melihat kunci awam, tetapi tidak boleh mengira rahsia bersama daripadanya. Ini bergantung pada kesukaran Masalah Logaritma Diskret Lengkung Eliptik (ECDLP).

Langkah 3: Mengira rahsia bersama

Setelah kedua-dua peranti menemui kunci awam satu sama lain, masing-masing mengira rahsia bersama:

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

yang sama. Matematik lengkung eliptik menjamin kedua-dua pengiraan memberikan hasil yang sama, walaupun setiap peranti hanya mengetahui kunci peribadinya sendiri dan kunci awam peranti lain.

Langkah 4: Nombor pengesahan (SAS)

Daripada rahsia bersama, Rentetan Pengesahan Pendek (Short Authentication String, SAS) diperoleh — nombor dua digit yang dipaparkan pada kedua-dua peranti:

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

Kedua-dua peranti memaparkan nombor yang sama — contohnya, 42. Pengguna membandingkan secara visual sama ada nombor pada kedua-dua skrin sepadan, kemudian mengesahkan pada setiap peranti secara berasingan.

Mengapa penyerang tidak boleh memalsukannya

Penyerang man-in-the-middle perlu memintas pertukaran kunci dalam Firebase. Secara khusus, mereka perlu:

  1. Menggantikan kunci awam sebenar yang disimpan dalam dokumen Firestore dengan kunci mereka sendiri
  2. Mewujudkan rahsia bersama berasingan dengan setiap peranti
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)
      

Pengesanan man-in-the-middle melalui ketidakpadanan SAS — sumber boleh disunting: docs/diagrams/mitm-detection.mmd

Dalam keadaan ini, penyerang mengira rahsia bersama S_A dengan Peranti A dan rahsia bersama yang berbeza S_B dengan Peranti B. Oleh sebab S_A ≠ S_B, peranti mengira nombor pengesahan yang berbeza.

Penyerang tidak dapat menjadikan nombor tersebut sepadan kerana:

Pengguna melihat nombor yang berbeza pada skrin lalu membatalkan pemadanan. Pada ketika itu, serangan tersebut sudah dapat dikesan.

Langkah 5: Menyelesaikan pemadanan

Hanya selepas pengguna mengesahkan nombor pengesahan pada kedua-dua peranti barulah pemadanan selesai:

  1. Satu kunci pemadanan 64 aksara (256 bit) diperoleh daripada rahsia bersama: SHA-256("pair:" + sharedSecret) → pairingKey
  2. Kunci dokumen diperoleh sebagai SHA-256("doc:" + pairingKey) dan digunakan sebagai kunci dokumen Firestore
  3. Kunci penyulitan diperoleh sebagai SHA-256("enc:" + pairingKey) dan menyediakan kunci AES-256-GCM untuk pensinyalan yang disulitkan
  4. Kedua-dua peranti menyimpan kunci pemadanan yang sama dan meneruskan ke skrin pemilihan mod

Mulai saat ini, semua percubaan sambungan seterusnya (pensinyalan Firestore dan persediaan WebRTC) disulitkan dengan kunci AES-256-GCM yang dikongsi. Kunci pemadanan tidak pernah dihantar ke sistem bahagian belakang; hanya cincangan SHA-256 bagi kunci itu digunakan sebagai pengecam dokumen.

Seni bina sistem

Rajah berikut menunjukkan komponen yang terlibat dalam pemadanan dan komunikasi:

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

Gambaran keseluruhan seni bina sistem — sumber boleh disunting: docs/diagrams/pairing-architecture.mmd

Laluan komunikasi secara terperinci:

Pilihan sandaran: Kemasukan kod secara manual

Jika Bluetooth tidak tersedia (contohnya, pada peranti lama), kod 4 aksara juga boleh dimasukkan secara manual. Kaedah ini menggunakan pertukaran kunci ECDH dan pengesahan SAS yang sama seperti pemadanan automatik. Satu-satunya perbezaan ialah kod dibaca dan dimasukkan oleh pengguna, bukannya ditemukan melalui BLE.

Oleh sebab pertukaran kunci ECDH berlaku melalui Firebase dalam kedua-dua keadaan, tahap keselamatannya adalah sama. Kod 4 aksara hanyalah titik pertemuan; penyulitan sebenar berasaskan kunci 256 bit yang diperoleh daripada ECDH.

Ringkasan

Mekanisme keselamatan Melindungi daripada
Pertukaran kunci ECDH (P-256) Pengintipan trafik pertukaran kunci
Pasangan kunci sementara Kerahsiaan ke hadapan — pemadanan terdahulu kekal selamat
Nombor pengesahan visual (SAS) Man-in-the-middle (MITM) semasa pertukaran kunci
Cincang SHA-256 sebagai kunci dokumen Pengambilan kod daripada Firestore
Penyulitan AES-256-GCM Pengintipan data pensinyalan
Pengesahan pada kedua-dua peranti Pemadanan satu pihak tanpa pengetahuan pengguna
DTLS-SRTP (WebRTC) Pengintipan audio/video

Lapisan ini saling melengkapi: ECDH melindungi pertukaran kunci, nombor pengesahan melindungi daripada MITM, AES-256-GCM melindungi pensinyalan dan WebRTC melindungi media. Penyerang perlu memecahkan rantaian ini di beberapa tempat tanpa peranti atau ibu bapa menyedarinya.


Artikel lain