Блог

Безпечне сполучення в Baby Monitor Timmy

Як разом працюють ECDH, перевірочне число SAS і зашифрована сигналізація.

Перш ніж 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):

Ключі створюються за допомогою криптографічно безпечного генератора випадкових чисел (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. Зокрема, йому потрібно було б:

  1. Замінити справжні відкриті ключі, збережені в документі Firestore, на власні
  2. Установити окремі спільні секрети з кожним пристроєм
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, пристрої обчислюють різні перевірочні числа.

Зловмисник не може змусити числа збігтися, тому що:

Користувач бачить різні числа на екранах і скасовує сполучення. У цей момент атака стає помітною.

Крок 5: Завершення сполучення

Лише після того, як користувач підтвердить перевірку на обох пристроях сполучення завершується:

  1. Виводиться 64-символьний ключ сполучення (256 біт) зі спільного секрету: SHA-256("pair:" + sharedSecret) → pairingKey
  2. Ключ документа виводиться як SHA-256("doc:" + pairingKey) і слугує ключем документа Firestore
  3. Ключ шифрування виводиться як SHA-256("enc:" + pairingKey) і надає ключ AES-256-GCM для зашифрованої сигналізації
  4. Обидва пристрої зберігають однаковий ключ сполучення та переходять до вибору режиму

Від цього моменту всі наступні спроби з’єднання (сигналізація 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

Детально про шляхи зв’язку:

Запасний варіант: ручне введення коду

Якщо 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 — медіадані. Зловмиснику довелося б зламати цей ланцюг у кількох місцях, не привернувши уваги пристроїв або батьків.


Більше статей