2025-06-20 23:19:45 +03:00

92 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Анализ и реализация клиента для Project Colette V53
Этот документ представляет подробный технический разбор процесса подключения к серверу Project Colette V53. Он анализирует криптографическое рукопожатие, структуру пакетов и логику обмена сообщениями, реализованную в Python-клиенте `main.py`.
## [*] Основные концепции
Протокол обмена данными — это пользовательский протокол, работающий поверх стандартного TCP. Шифрование построено с использованием криптографической библиотеки **NaCl** (в коде C# упоминается как `TweetNaCl`), которая предоставляет:
- **Асимметричное шифрование**: `Curve25519` для обмена ключами через эллиптическую кривую Диффи-Хеллмана (ECDH).
- **Симметричное шифрование**: `XSalsa20-Poly1305` для потокового шифрования после установления сессии.
- **Хеширование**: `Blake2b` для генерации одноразовых номеров (nonce).
Каждое сообщение (пакет) в протоколе предваряется 7-байтным заголовком:
- **ID сообщения** (2 байта): Идентификатор типа сообщения.
- **Длина полезной нагрузки** (3 байта): Длина предстоящих данных.
- **Версия** (2 байта): Версия сообщения.
## [!] Пошаговый разбор процесса подключения
Процесс от первоначального подключения до полностью установленной зашифрованной сессии включает обмен четырьмя ключевыми пакетами.
---
### **Шаг 1: `ClientHello` (ID: 10100)**
- **Направление**: Клиент -> Сервер
- **Шифрование**: Нет
Это самый первый пакет, который клиент отправляет после установления TCP-соединения. Его единственная цель — инициировать сессию на сервере и сообщить о готовности начать рукопожатие. Полезная нагрузка этого пакета не имеет значения для криптографии и может быть заполнена нулями.
---
### **Шаг 2: `LoginMessage` (ID: 10101)**
- **Направление**: Клиент -> Сервер
- **Шифрование**: Асимметричное (`Box`)
Это ключевой пакет от клиента, где происходит основная магия обмена ключами.
#### **Подготовка на стороне клиента (перед отправкой):**
1. **Ключ сервера**: Клиент использует статический `server_public_key` сервера, который известен заранее (извлечен из исходного кода сервера).
2. **Ключи клиента**: Клиент генерирует новую, **эфемерную (временную)** пару ключей `Curve25519``client_public_key` и `client_private_key`. Эта пара будет использоваться только для текущей сессии.
3. **Общий секрет (`s`)**: Клиент немедленно вычисляет предварительный общий секрет (`shared_secret` или `s`), используя свой новый приватный ключ и публичный ключ сервера. Это стандартная операция ECDH. В `PyNaCl` это делается с помощью `Box.beforenm(server_public_key, client_private_key)`. Этот секрет `s` понадобится для расшифровки ответа сервера.
4. **Nonce клиента (`RNonce`)**: Клиент генерирует 24 случайных байта, `client_nonce`. Сервер называет это `RNonce` (Nonce получателя). Это критически важная часть рукопожатия.
#### **Структура и передача пакета:**
Пакет `10101` состоит из двух частей:
1. **Публичный ключ клиента** (32 байта): Отправляется в открытом виде.
2. **Зашифрованная полезная нагрузка**:
- **Данные для шифрования**: Открытый текст для шифрования содержит:
1. Временный сеансовый ключ (24 байта, генерируется клиентом, но игнорируется сервером).
2. `client_nonce` (`RNonce`) (24 байта).
3. Фактические данные для входа (ID аккаунта, версия клиента и т.д.), сериализованные в байтовый поток.
- **Nonce для шифрования**: Nonce для этой операции вычисляется как хеш публичных ключей: `blake2b(client_public_key + server_public_key)`.
- **Процесс шифрования**: Вышеуказанные данные шифруются с помощью `Box(client_private_key, server_public_key).encrypt(...)`.
---
### **Шаг 3: `ServerHello` (ID: 20100)**
- **Направление**: Сервер -> Клиент
- **Шифрование**: Асимметричное (`Box`)
Это ответ сервера, который завершает асимметричную фазу рукопожатия.
#### **Процесс расшифровки на стороне клиента:**
1. **Вычисление Nonce**: Чтобы расшифровать этот пакет, клиент должен использовать тот же `nonce`, который использовал сервер. Сервер вычисляет его по формуле: `blake2b(RNonce + client_public_key + server_public_key)`. У клиента уже есть все три компонента: он сам сгенерировал `RNonce` и знает оба публичных ключа.
2. **Процесс расшифровки**: Расшифровка выполняется с использованием ранее вычисленного общего секрета `s`. Формула: `Box.open_afternm(payload, nonce, shared_secret)`.
#### **Содержимое расшифрованного пакета:**
Внутри находятся два ключевых элемента для всей будущей коммуникации:
1. **Финальный сеансовый ключ (`session_key`)** (32 байта): Это симметричный ключ, который будет использоваться для шифрования всех последующих сообщений.
2. **Nonce сервера (`SNonce`)** (24 байта): Этот nonce будет основой для шифрования сообщений, идущих **от сервера к клиенту**.
---
### **Шаг 4: Переход к симметричному шифрованию и `LoginOk` (ID: 20104)**
- **Направление**: Сервер -> Клиент
- **Шифрование**: Симметричное (`SecretBox`)
После получения `ServerHello` криптографическая сессия считается полностью установленной. Все последующие пакеты шифруются симметрично с использованием `SecretBox` и `session_key`.
#### **Логика Nonce (`NextNonce`)**
Ключевой особенностью протокола является то, что nonce увеличивается **на 2** перед каждой операцией шифрования или дешифрования. Функция `next_nonce` в коде точно воспроизводит эту логику на стороне сервера.
#### **`LoginOk`**
Это первый пакет, который клиент получает в зашифрованном виде.
1. **Получить Nonce**: Клиент берет `SNonce`, полученный на предыдущем шаге.
2. **Увеличить Nonce**: Он применяет к нему логику `next_nonce` (увеличивая его на 2).
3. **Расшифровать**: Он расшифровывает пакет с помощью `SecretBox(session_key).decrypt(payload, nonce)`.
Успешная расшифровка `LoginOk` подтверждает, что весь процесс прошел корректно, и клиент теперь находится в полностью зашифрованной сессии с сервером. Этот пакет содержит данные о созданном аккаунте (ID, токен и т.д.).