9.4 KiB
Анализ и реализация клиента для 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, токен и т.д.).