Перш чым Baby Monitor Timmy пачне перадаваць аўдыё і відэа, дзве прылады павінны знайсці адна адну і ўсталяваць давер. Гэты этап спалучэння — самы адказны момант ва ўсім працэсе. Тут я тлумачу, як Timmy спалучае прылады, якія крыптаграфічныя механізмы для гэтага выкарыстоўваюцца і чаму нападнік паблізу не можа незаўважна перахапіць злучэнне.
Праблема: як мая прылада ведае, з кім яна размаўляе?
Калі дзве прылады падключаюцца ўпершыню, галоўнае пытанне такое: ці сапраўды прылада A размаўляе з прыладай B, ці нехта знаходзіцца пасярэдзіне? У крыптаграфіі гэта называецца атакай «чалавек пасярэдзіне» (MITM).
Timmy вырашае гэта з дапамогай абмену ключамі Дыфі — Хелмана на эліптычных крывых (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. Ключ спалучэння ніколі не перадаецца на серверную частку сістэмы; у якасці ідэнтыфікатара дакумента выкарыстоўваецца толькі яго хэш 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 або Cloudflare TURN рэтранслюе зашыфраваныя медыядадзеныя. Кароткатэрміновыя ўліковыя даныя (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 як ключ дакумента | Атрымання кода з Firestore |
| Шыфраванне AES-256-GCM | Перахопу даных сігналізацыі |
| Пацвярджэнне з абодвух бакоў | Аднабаковага спалучэння без ведама карыстальніка |
| DTLS-SRTP (WebRTC) | Перахопу аўдыё/відэа |
Гэтыя ўзроўні працуюць разам: ECDH абараняе абмен ключамі, нумар праверкі — ад MITM, AES-256-GCM — сігналізацыю, а WebRTC — медыя. Нападніку давялося б зламаць гэты ланцужок у некалькіх месцах так, каб прылады або бацькі гэтага не заўважылі.