სანამ Baby Monitor Timmy აუდიოსა და ვიდეოს გადასცემს, ორმა მოწყობილობამ უნდა იპოვოს ერთმანეთი და ენდოს ერთმანეთს. ეს დაწყვილების ეტაპი მთელი პროცესის ყველაზე კრიტიკული მომენტია. აქ ავხსნი, როგორ აწყვილებს Timmy მოწყობილობებს, რა კრიპტოგრაფია უდევს ამას საფუძვლად და რატომ ვერ ჩაიგდებს ახლომყოფი თავდამსხმელი კავშირს შეუმჩნევლად ხელში.
პრობლემა: საიდან იცის ჩემმა მოწყობილობამ, ვის ესაუბრება?
როდესაც ორი მოწყობილობა პირველად უკავშირდება ერთმანეთს, მთავარი კითხვაა: მართლა B მოწყობილობას ესაუბრება A მოწყობილობა თუ ვიღაც შუაშია? კრიპტოგრაფიაში ამას ეწოდება შუამავალის შეტევა (MITM).
Timmy ამას აგვარებს ელიფსური მრუდის 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 peer-to-peer (სქელი ხაზი): აუდიო, ვიდეო და DataChannel პირდაპირ მიედინება მოწყობილობებს შორის — დაშიფრულია DTLS-SRTP-ით. ამ მონაცემებს არცერთი სერვერი არ ხედავს.
- Firebase Firestore (უწყვეტი ხაზი): დაწყვილების მონაცემები (ECDH გასაღებები) და სასიგნალო მონაცემები (SDP/ICE) Firestore-ის მეშვეობით გადაიცემა და AES-256-GCM-ით გამჭოლად იშიფრება. Firebase ამ მონაცემებს ვერ გაშიფრავს.
- STUN სერვერი: ორივე მოწყობილობა იგებს თავის საჯარო IP მისამართს, რათა პირდაპირი peer-to-peer კავშირი დამყარდეს.
- 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 იცავს გასაღებების გაცვლას, შემოწმების ნომერი — MITM შეტევისგან, AES-256-GCM — სასიგნალო მონაცემებს, ხოლო WebRTC — მედიას. თავდამსხმელს ამ ჯაჭვის რამდენიმე რგოლის გარღვევა მოუწევდა ისე, რომ მოწყობილობებს ან მშობლებს ეს ვერ შეემჩნიათ.