Анализ и реализация клиента для 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)
Это ключевой пакет от клиента, где происходит основная магия обмена ключами.
Подготовка на стороне клиента (перед отправкой):
- Ключ сервера: Клиент использует статический
server_public_keyсервера, который известен заранее (извлечен из исходного кода сервера). - Ключи клиента: Клиент генерирует новую, эфемерную (временную) пару ключей
Curve25519—client_public_keyиclient_private_key. Эта пара будет использоваться только для текущей сессии. - Общий секрет (
s): Клиент немедленно вычисляет предварительный общий секрет (shared_secretилиs), используя свой новый приватный ключ и публичный ключ сервера. Это стандартная операция ECDH. ВPyNaClэто делается с помощьюBox.beforenm(server_public_key, client_private_key). Этот секретsпонадобится для расшифровки ответа сервера. - Nonce клиента (
RNonce): Клиент генерирует 24 случайных байта,client_nonce. Сервер называет этоRNonce(Nonce получателя). Это критически важная часть рукопожатия.
Структура и передача пакета:
Пакет 10101 состоит из двух частей:
- Публичный ключ клиента (32 байта): Отправляется в открытом виде.
- Зашифрованная полезная нагрузка:
- Данные для шифрования: Открытый текст для шифрования содержит:
- Временный сеансовый ключ (24 байта, генерируется клиентом, но игнорируется сервером).
client_nonce(RNonce) (24 байта).- Фактические данные для входа (ID аккаунта, версия клиента и т.д.), сериализованные в байтовый поток.
- Nonce для шифрования: Nonce для этой операции вычисляется как хеш публичных ключей:
blake2b(client_public_key + server_public_key). - Процесс шифрования: Вышеуказанные данные шифруются с помощью
Box(client_private_key, server_public_key).encrypt(...).
- Данные для шифрования: Открытый текст для шифрования содержит:
Шаг 3: ServerHello (ID: 20100)
- Направление: Сервер -> Клиент
- Шифрование: Асимметричное (
Box)
Это ответ сервера, который завершает асимметричную фазу рукопожатия.
Процесс расшифровки на стороне клиента:
- Вычисление Nonce: Чтобы расшифровать этот пакет, клиент должен использовать тот же
nonce, который использовал сервер. Сервер вычисляет его по формуле:blake2b(RNonce + client_public_key + server_public_key). У клиента уже есть все три компонента: он сам сгенерировалRNonceи знает оба публичных ключа. - Процесс расшифровки: Расшифровка выполняется с использованием ранее вычисленного общего секрета
s. Формула:Box.open_afternm(payload, nonce, shared_secret).
Содержимое расшифрованного пакета:
Внутри находятся два ключевых элемента для всей будущей коммуникации:
- Финальный сеансовый ключ (
session_key) (32 байта): Это симметричный ключ, который будет использоваться для шифрования всех последующих сообщений. - Nonce сервера (
SNonce) (24 байта): Этот nonce будет основой для шифрования сообщений, идущих от сервера к клиенту.
Шаг 4: Переход к симметричному шифрованию и LoginOk (ID: 20104)
- Направление: Сервер -> Клиент
- Шифрование: Симметричное (
SecretBox)
После получения ServerHello криптографическая сессия считается полностью установленной. Все последующие пакеты шифруются симметрично с использованием SecretBox и session_key.
Логика Nonce (NextNonce)
Ключевой особенностью протокола является то, что nonce увеличивается на 2 перед каждой операцией шифрования или дешифрования. Функция next_nonce в коде точно воспроизводит эту логику на стороне сервера.
LoginOk
Это первый пакет, который клиент получает в зашифрованном виде.
- Получить Nonce: Клиент берет
SNonce, полученный на предыдущем шаге. - Увеличить Nonce: Он применяет к нему логику
next_nonce(увеличивая его на 2). - Расшифровать: Он расшифровывает пакет с помощью
SecretBox(session_key).decrypt(payload, nonce).
Успешная расшифровка LoginOk подтверждает, что весь процесс прошел корректно, и клиент теперь находится в полностью зашифрованной сессии с сервером. Этот пакет содержит данные о созданном аккаунте (ID, токен и т.д.).