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 peribadi — hanya kekal pada peranti
- Kunci awam — ditukar melalui Firebase
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:
- Menggantikan kunci awam sebenar yang disimpan dalam dokumen Firestore dengan kunci mereka sendiri
- 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:
- Penyerang tidak mengetahui kunci peribadi peranti
- Cincangan SHA-256 tidak boleh disongsangkan
- Kebarangkalian padanan rawak hanyalah 1 dalam 100
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:
- Satu kunci pemadanan 64 aksara (256 bit) diperoleh daripada rahsia bersama:
SHA-256("pair:" + sharedSecret) → pairingKey - Kunci dokumen diperoleh sebagai
SHA-256("doc:" + pairingKey)dan digunakan sebagai kunci dokumen Firestore - Kunci penyulitan diperoleh sebagai
SHA-256("enc:" + pairingKey)dan menyediakan kunci AES-256-GCM untuk pensinyalan yang disulitkan - 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:
- WebRTC peer-to-peer (garisan tebal): Audio, video dan DataChannel mengalir terus antara peranti — disulitkan dengan DTLS-SRTP. Data ini tidak dilihat oleh mana-mana pelayan.
- Firebase Firestore (garisan padu): Data pemadanan (kunci ECDH) dan pensinyalan (SDP/ICE) melalui Firestore — disulitkan dari hujung ke hujung dengan AES-256-GCM. Firebase tidak dapat menyahsulit data tersebut.
- Pelayan STUN: Kedua-dua peranti mengetahui alamat IP awam masing-masing supaya sambungan peer-to-peer secara terus dapat diwujudkan.
- Pelayan geganti TURN: Jika sambungan terus tidak dapat dibuat (contohnya, apabila menggunakan data mudah alih), pelayan TURN tempatan atau Cloudflare yang dipilih akan menyampaikan media yang disulitkan. Bukti kelayakan jangka pendek (24 jam) diperoleh melalui Firebase Cloud Functions.
- Bluetooth LE (garisan bertitik): Nearby Connections mencari peranti berdekatan secara automatik — hanya kod pertemuan dihantar, tanpa sebarang bahan kunci.
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.