Նախքան 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 բանալիով։ Զուգակցման բանալին երբեք չի ուղարկվում սերվերային համակարգ։ Որպես փաստաթղթի նույնացուցիչ օգտագործվում է միայն դրա 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 միջնորդ սերվեր․ եթե ուղիղ կապը հնարավոր չէ (օրինակ՝ բջջային տվյալների ցանցում), ընտրված տեղային կամ Cloudflare TURN սերվերը վերահաղորդում է գաղտնագրված մեդիա տվյալները։ 24 ժամ գործող կարճաժամկետ նույնականացման տվյալները ստացվում են Firebase Cloud Functions-ի միջոցով։
- Bluetooth LE (կետավոր գիծ). Nearby Connections-ը մոտակա սարքերն ավտոմատ է հայտնաբերում։ Փոխանցվում է միայն հանդիպման կոդը, ոչ թե բանալիների հետ կապված տվյալներ։
Պահուստային տարբերակ. կոդի ձեռքով մուտքագրում
Եթե Bluetooth-ը հասանելի չէ (օրինակ՝ հին սարքերում), 4 գրանշանից բաղկացած կոդը կարելի է նաև ձեռքով մուտքագրել։ Ձեռքով մուտքագրումն օգտագործում է ECDH բանալիների նույն փոխանակումը և SAS-ի նույն ստուգումը ինչ ավտոմատ զուգակցումը։ Միակ տարբերությունն այն է, որ կոդը կարդում և մուտքագրում է օգտատերը՝ BLE-ով այն հայտնաբերելու փոխարեն։
Քանի որ երկու դեպքում էլ ECDH բանալիների փոխանակումը կատարվում է Firebase-ով, անվտանգությունը նույնականէ։ 4 գրանշանից բաղկացած կոդը միայն հանդիպման կետ է. իրական գաղտնագրումը հիմնված է ECDH-ից ստացված 256-բիթանոց բանալու վրա։
Ամփոփում
| Անվտանգության մեխանիզմ | Ինչից է պաշտպանում |
|---|---|
| ECDH բանալիների փոխանակում (P-256) | Բանալիների փոխանակման երթևեկության գաղտնալսումից |
| Ժամանակավոր բանալիների զույգեր | Առաջանցիկ գաղտնիություն — նախկին զուգակցումները մնում են անվտանգ |
| Տեսողական ստուգման թիվ (SAS) | Բանալիների փոխանակման ընթացքում միջանկյալ անձի (MITM) հարձակումից |
| SHA-256 հեշը՝ որպես փաստաթղթի բանալի | Firestore-ից կոդը կորզելու փորձերից |
| AES-256-GCM գաղտնագրում | Ազդանշանման տվյալների գաղտնալսումից |
| Երկու կողմերի հաստատում | Առանց օգտատիրոջ իմացության միակողմանի զուգակցումից |
| DTLS-SRTP (WebRTC) | Ձայնի և տեսանյութի գաղտնալսումից |
Այս շերտերը լրացնում են միմյանց. ECDH-ը պաշտպանում է բանալիների փոխանակումը, ստուգման թիվը՝ միջանկյալ անձի հարձակումից, AES-256-GCM-ը՝ ազդանշանման տվյալները, իսկ WebRTC-ը՝ մեդիա տվյալները։ Հարձակվողը պետք է մի քանի կետում խախտի այս պաշտպանական շղթան՝ առանց սարքերի կամ ծնողների կողմից նկատվելու։