ກ່ອນທີ່ Baby Monitor Timmy ຈະສົ່ງສຽງ ແລະວິດີໂອ, ອຸປະກອນສອງເຄື່ອງຕ້ອງຊອກຫາກັນ ແລະໄວ້ໃຈກັນ. ຂັ້ນຕອນ ການຈັບຄູ່ ນີ້ແມ່ນຊ່ວງເວລາສຳຄັນທີ່ສຸດໃນຂັ້ນຕອນທັງໝົດ. ທີ່ນີ້ຂ້ອຍອະທິບາຍວ່າ Timmy ຈັບຄູ່ແນວໃດ, ມີການເຂົ້າລະຫັດໃດຢູ່ເບື້ອງຫຼັງ ແລະເປັນຫຍັງຜູ້ໂຈມຕີທີ່ຢູ່ໃກ້ຈຶ່ງບໍ່ສາມາດຍຶດການເຊື່ອມຕໍ່ໄດ້ໂດຍບໍ່ຖືກສັງເກດ.
ບັນຫາ: ອຸປະກອນຂອງຂ້ອຍຮູ້ໄດ້ແນວໃດວ່າກຳລັງສື່ສານກັບໃຜ?
ເມື່ອອຸປະກອນສອງເຄື່ອງເຊື່ອມຕໍ່ກັນຄັ້ງທຳອິດ, ຄຳຖາມຫຼັກແມ່ນ: ອຸປະກອນ A ກຳລັງສື່ສານກັບອຸປະກອນ B ແທ້ບໍ, ຫຼືມີຜູ້ໃດໜຶ່ງຢູ່ກາງ? ໃນວິຊາການເຂົ້າລະຫັດ ນີ້ເອີ້ນວ່າການໂຈມຕີ ຜູ້ຢູ່ກາງ (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):
- ກະແຈ ສ່ວນຕົວ — ຢູ່ໃນອຸປະກອນເທົ່ານັ້ນ
- ກະແຈ ສາທາລະນະ — ແລກປ່ຽນຜ່ານ Firebase
ກະແຈຖືກສ້າງດ້ວຍຕົວສ້າງຕົວເລກສຸ່ມທີ່ປອດໄພທາງການເຂົ້າລະຫັດ (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. ໂດຍສະເພາະ ພວກເຂົາຈະຕ້ອງ:
- ປ່ຽນກະແຈສາທາລະນະແທ້ທີ່ເກັບໄວ້ໃນເອກະສານ Firestore ເປັນຂອງຕົນເອງ
- ສ້າງຄວາມລັບທີ່ໃຊ້ຮ່ວມກັນແຍກກັບແຕ່ລະອຸປະກອນ
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 ກັບອຸປະກອນ A ແລະຄວາມລັບອີກອັນໜຶ່ງ S_B ກັບອຸປະກອນ B. ເນື່ອງຈາກ S_A ≠ S_B,
ອຸປະກອນຈຶ່ງຄຳນວນ ຕົວເລກຢືນຢັນທີ່ຕ່າງກັນ.
ຜູ້ໂຈມຕີບໍ່ສາມາດເຮັດໃຫ້ຕົວເລກກົງກັນໄດ້ ເພາະວ່າ:
- ພວກເຂົາບໍ່ຮູ້ກະແຈສ່ວນຕົວຂອງອຸປະກອນ
- SHA-256 ບໍ່ສາມາດຖອດກັບໄດ້
- ໂອກາດທີ່ຈະກົງກັນແບບສຸ່ມມີພຽງ 1 ໃນ 100
ຜູ້ໃຊ້ຈະເຫັນຕົວເລກຕ່າງກັນໃນໜ້າຈໍ ແລະຍົກເລີກການຈັບຄູ່. ເມື່ອນັ້ນ ການໂຈມຕີກໍຖືກເປີດເຜີຍ.
ຂັ້ນຕອນ 5: ສຳເລັດການຈັບຄູ່
ຫຼັງຈາກຜູ້ໃຊ້ຢືນຢັນການກວດສອບໃນ ທັງສອງອຸປະກອນ ເທົ່ານັ້ນ ການຈັບຄູ່ຈຶ່ງສຳເລັດ:
- ກະແຈຈັບຄູ່ ຄວາມຍາວ 64 ຕົວອັກສອນ (256 ບິດ) ຖືກສ້າງຈາກຄວາມລັບທີ່ໃຊ້ຮ່ວມກັນ:
SHA-256("pair:" + sharedSecret) → pairingKey - ກະແຈເອກະສານຖືກສ້າງເປັນ
SHA-256("doc:" + pairingKey)ແລະໃຊ້ເປັນກະແຈເອກະສານ Firestore - ກະແຈເຂົ້າລະຫັດຖືກສ້າງເປັນ
SHA-256("enc:" + pairingKey)ແລະໃຫ້ກະແຈ AES-256-GCM ສຳລັບການສົ່ງສັນຍານທີ່ເຂົ້າລະຫັດ - ອຸປະກອນທັງສອງເກັບກະແຈຈັບຄູ່ດຽວກັນ ແລະໄປຫາໜ້າເລືອກໂໝດ
ນັບແຕ່ຈຸດນີ້ໄປ, ຄວາມພະຍາຍາມເຊື່ອມຕໍ່ຕໍ່ໄປທັງໝົດ (ການສົ່ງສັນຍານ Firestore, ການຕັ້ງຄ່າ WebRTC) ຖືກເຂົ້າລະຫັດດ້ວຍກະແຈ AES-256-GCM ທີ່ໃຊ້ຮ່ວມກັນ. ກະແຈຈັບຄູ່ ບໍ່ເຄີຍຖືກສົ່ງໄປຫາລະບົບຫຼັງບ້ານ (backend); ຈະໃຊ້ພຽງຄ່າແຮຊ 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
ລາຍລະອຽດເສັ້ນທາງການສື່ສານ:
- WebRTC ແບບເພື່ອນຫາເພື່ອນ (ເສັ້ນໜາ): ສຽງ, ວິດີໂອ ແລະ DataChannel ໄຫຼກົງລະຫວ່າງອຸປະກອນ — ເຂົ້າລະຫັດດ້ວຍ DTLS-SRTP. ບໍ່ມີເຊີບເວີໃດເຫັນຂໍ້ມູນນີ້.
- Firebase Firestore (ເສັ້ນທຶບ): ຂໍ້ມູນການຈັບຄູ່ (ກະແຈ ECDH) ແລະການສົ່ງສັນຍານ (SDP/ICE) ຜ່ານ Firestore — ເຂົ້າລະຫັດແບບຕົ້ນທາງຫາປາຍທາງດ້ວຍ AES-256-GCM. Firebase ບໍ່ສາມາດຖອດລະຫັດຂໍ້ມູນໄດ້.
- ເຊີບເວີ STUN: ອຸປະກອນທັງສອງຄົ້ນພົບທີ່ຢູ່ IP ສາທາລະນະຂອງຕົນ ເພື່ອໃຫ້ສ້າງການເຊື່ອມຕໍ່ແບບເພື່ອນຫາເພື່ອນໂດຍກົງໄດ້.
- ຕົວສົ່ງຕໍ່ TURN: ຖ້າບໍ່ສາມາດເຊື່ອມຕໍ່ໂດຍກົງໄດ້ (ເຊັ່ນ ເມື່ອໃຊ້ອິນເຕີເນັດມືຖື), ເຊີບເວີ TURN ພາຍໃນທີ່ເລືອກໄວ້ ຫຼືເຊີບເວີ TURN ຂອງ Cloudflare ຈະສົ່ງຕໍ່ສື່ທີ່ເຂົ້າລະຫັດແລ້ວ. ຂໍ້ມູນຢືນຢັນທີ່ມີອາຍຸສັ້ນ (24 ຊົ່ວໂມງ) ຈະຖືກຮັບມາຜ່ານ Firebase Cloud Functions.
- Bluetooth LE (ເສັ້ນຈຸດ): Nearby Connections ຄົ້ນພົບອຸປະກອນທີ່ຢູ່ໃກ້ໂດຍອັດຕະໂນມັດ — ສົ່ງພຽງລະຫັດນັດພົບ, ບໍ່ມີຂໍ້ມູນກະແຈ.
ທາງເລືອກ: ໃສ່ລະຫັດເອງ
ຖ້າ Bluetooth ໃຊ້ບໍ່ໄດ້ (ເຊັ່ນ ໃນອຸປະກອນເກົ່າ), ສາມາດພິມລະຫັດ 4 ຕົວອັກສອນເອງໄດ້. ການໃສ່ເອງໃຊ້ ການແລກປ່ຽນກະແຈ ECDH ແລະການຢືນຢັນ SAS ແບບດຽວກັນ ກັບການຈັບຄູ່ອັດຕະໂນມັດ. ຄວາມຕ່າງພຽງຢ່າງດຽວຄື ຜູ້ໃຊ້ອ່ານ ແລະພິມລະຫັດ ແທນທີ່ຈະຄົ້ນພົບຜ່ານ BLE.
ເນື່ອງຈາກການແລກປ່ຽນກະແຈ ECDH ເກີດຂຶ້ນຜ່ານ Firebase ໃນທັງສອງກໍລະນີ, ຄວາມປອດໄພຈຶ່ງ ຄືກັນ. ລະຫັດ 4 ຕົວອັກສອນເປັນພຽງຈຸດນັດພົບ; ການເຂົ້າລະຫັດທີ່ແທ້ຈິງອາໄສກະແຈ 256 ບິດທີ່ໄດ້ຈາກ ECDH.
ສະຫຼຸບ
| ກົນໄກຄວາມປອດໄພ | ປ້ອງກັນຈາກ |
|---|---|
| ການແລກປ່ຽນກະແຈ ECDH (P-256) | ການດັກຟັງຂໍ້ມູນການແລກປ່ຽນກະແຈ |
| ຄູ່ກະແຈຊົ່ວຄາວ | ຄວາມລັບໄປຂ້າງໜ້າ — ການຈັບຄູ່ທີ່ຜ່ານມາຍັງຄົງປອດໄພ |
| ຕົວເລກຢືນຢັນດ້ວຍຕາ (SAS) | ຜູ້ຢູ່ກາງ (MITM) ໃນລະຫວ່າງການແລກປ່ຽນກະແຈ |
| SHA-256 hash ເປັນກະແຈເອກະສານ | ການດຶງລະຫັດອອກຈາກ Firestore |
| ການເຂົ້າລະຫັດ AES-256-GCM | ການດັກຟັງຂໍ້ມູນສົ່ງສັນຍານ |
| ການຢືນຢັນຈາກທັງສອງຝ່າຍ | ການຈັບຄູ່ຝ່າຍດຽວໂດຍຜູ້ໃຊ້ບໍ່ຮູ້ |
| DTLS-SRTP (WebRTC) | ການດັກຟັງສຽງ/ວິດີໂອ |
ຊັ້ນປ້ອງກັນເຫຼົ່ານີ້ເຮັດວຽກຮ່ວມກັນ: ECDH ປົກປ້ອງການແລກປ່ຽນກະແຈ, ຕົວເລກຢືນຢັນປ້ອງກັນ MITM, AES-256-GCM ປົກປ້ອງການສົ່ງສັນຍານ, ແລະ WebRTC ປົກປ້ອງສື່. ຜູ້ໂຈມຕີຈະຕ້ອງທຳລາຍຫ່ວງໂສ້ນີ້ຫຼາຍຈຸດ ໂດຍບໍ່ໃຫ້ອຸປະກອນ ຫຼືພໍ່ແມ່ຮູ້ຕົວ.