5.7 KiB
Analysis and Client Implementation for Project Colette V53
This document provides a detailed technical breakdown of the connection process to a Project Colette V53 server. It dissects the cryptographic handshake, packet structure, and message exchange logic as implemented in the main.py
Python client.
[*] Core Concepts
The data exchange protocol is a custom protocol layered on top of standard TCP. The encryption is built using the NaCl cryptographic library (referenced in the C# code as TweetNaCl
), which provides:
- Asymmetric Encryption:
Curve25519
for key exchange via Elliptic-curve Diffie–Hellman (ECDH). - Symmetric Encryption:
XSalsa20-Poly1305
for stream encryption after the session is established. - Hashing:
Blake2b
for nonce generation.
Each message (packet) in the protocol is prefixed with a 7-byte header:
- Message ID (2 bytes): The identifier for the message type.
- Payload Length (3 bytes): The length of the upcoming payload.
- Version (2 bytes): The version of the message.
[!] Step-by-Step Breakdown of the Connection Process
The process from the initial connection to a fully established encrypted session involves the exchange of four key packets.
Step 1: ClientHello
(ID: 10100)
- Direction: Client -> Server
- Encryption: None
This is the very first packet the client sends after establishing a TCP connection. Its sole purpose is to initiate a session on the server and signal readiness to begin the handshake. The payload of this packet is irrelevant to the cryptography and can be filled with zeros.
Step 2: LoginMessage
(ID: 10101)
- Direction: Client -> Server
- Encryption: Asymmetric (
Box
)
This is the key packet from the client, where the core of the key exchange magic happens.
Client-Side Preparation (Before Sending):
- Server's Key: The client uses the server's static
server_public_key
, which is known beforehand (extracted from the server's source code). - Client's Keys: The client generates a new, ephemeral (temporary)
Curve25519
key pair—client_public_key
andclient_private_key
. This pair will only be used for the current session. - Shared Secret (
s
): The client immediately computes the pre-master shared secret (shared_secret
ors
) using its new private key and the server's public key. This is a standard ECDH operation. InPyNaCl
, this is done withBox.beforenm(server_public_key, client_private_key)
. This secrets
will be needed to decrypt the server's response. - Client Nonce (
RNonce
): The client generates 24 random bytes, theclient_nonce
. The server refers to this as theRNonce
(Receiver's Nonce). It is a critical part of the handshake.
Packet Structure and Transmission:
The 10101
packet consists of two parts:
- Client's Public Key (32 bytes): Sent in cleartext.
- Encrypted Payload:
- Data to Encrypt: The plaintext to be encrypted contains:
- A temporary session key (24 bytes, generated by the client but ignored by the server).
- The
client_nonce
(RNonce
) (24 bytes). - The actual login data (account ID, client version, etc.), serialized into a bytestream.
- Nonce for Encryption: The nonce for this operation is calculated as a hash of the public keys:
blake2b(client_public_key + server_public_key)
. - Encryption Process: The data above is encrypted using
Box(client_private_key, server_public_key).encrypt(...)
.
- Data to Encrypt: The plaintext to be encrypted contains:
Step 3: ServerHello
(ID: 20100)
- Direction: Server -> Client
- Encryption: Asymmetric (
Box
)
This is the server's response, which completes the asymmetric phase of the handshake.
Client-Side Decryption Process:
- Nonce Calculation: To decrypt this packet, the client must use the same
nonce
the server used. The server calculates it with the formula:blake2b(RNonce + client_public_key + server_public_key)
. The client already has all three components: it generatedRNonce
itself, and it knows both public keys. - Decryption Process: Decryption is performed using the previously computed shared secret
s
. Formula:Box.open_afternm(payload, nonce, shared_secret)
.
Contents of the Decrypted Packet:
Inside are two key elements for all future communication:
- Final Session Key (
session_key
) (32 bytes): This is the symmetric key that will be used to encrypt all subsequent messages. - Server Nonce (
SNonce
) (24 bytes): This nonce will be the base for encrypting messages flowing from the server to the client.
Step 4: Transition to Symmetric Encryption & LoginOk
(ID: 20104)
- Direction: Server -> Client
- Encryption: Symmetric (
SecretBox
)
After receiving ServerHello
, the cryptographic session is fully established. All subsequent packets are encrypted symmetrically using SecretBox
and the session_key
.
Nonce Logic (NextNonce
)
A key feature of the protocol is that the nonce is incremented by 2 before every encryption or decryption operation. The next_nonce
function in the code precisely replicates this server-side logic.
LoginOk
This is the first packet the client receives that is symmetrically encrypted.
- Get Nonce: The client takes the
SNonce
it received in the previous step. - Increment Nonce: It applies the
next_nonce
logic to it (increasing it by 2). - Decrypt: It decrypts the packet using
SecretBox(session_key).decrypt(payload, nonce)
.
Successfully decrypting LoginOk
confirms that the entire process has worked correctly, and the client is now in a fully encrypted session with the server. This packet contains the details of the newly created account (ID, token, etc.).