minecraft_protocol: foundation + per-version protocol docs 1.7.10->26.2

8 topical docs (overview, data types, lifecycle, handshake, status/ping,
login+encryption, configuration, version-differences) + proxy-forwarding set
+ 16 per-version release-line docs, sourced from minecraft.wiki, ViaVersion
(source + commits), minecraft-data, node-minecraft-protocol, Velocity, BungeeCord.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude-timemachine
2026-06-19 14:15:32 +02:00
commit d73c1c9537
35 changed files with 8894 additions and 0 deletions
+619
View File
@@ -0,0 +1,619 @@
# 05 — Login State, Encryption & Compression
> **Scope:** Java Edition 1.7.10 → latest (1.21.x). Covers the Login protocol state,
> the encryption handshake, Mojang session auth, packet compression, and the 1.19/1.20.2
> versioned deviations. All crypto details are sourced from the reference implementations
> listed in each section.
---
## 1. Login State Overview
The Login state begins immediately after the client sends a Handshake packet with
`next state = 2`. It ends when the server sends **Login Success** (and, since 1.20.2,
the client acknowledges it). All packets in this state use the standard framing:
```
<Packet Length: VarInt> <Packet ID: VarInt> <Data: bytes>
```
After **Set Compression** is negotiated (if at all), each packet gains an extra
**Data Length** VarInt (see §4). After **Encryption Response** is processed, the entire
TCP stream — including the Length prefix — is encrypted with AES/CFB8 (see §3).
---
## 2. Packet Reference
### 2.1 Login Start (0x00 Serverbound)
The first Login-state packet sent by the client.
| Field | Type | Version range | Notes |
|---|---|---|---|
| Username | String(16) | all | Player username, max 16 chars |
| Signature | Optional container | 1.191.19.2 | Profile public key; see §5 |
| └ Timestamp | i64 | 1.191.19.2 | Key expiry timestamp (ms since epoch) |
| └ Public Key | Prefixed byte array | 1.191.19.2 | DER SubjectPublicKeyInfo of player's key |
| └ Signature | Prefixed byte array | 1.191.19.2 | Mojang-signed; see §5 |
| Has UUID | Optional bool (not present in 1.20.2+) | 1.19.11.20.1 | Presence flag |
| Player UUID | UUID | 1.19.1+ | Optional (flag-gated) in 1.19.11.20.1; always present 1.20.2+ |
**Source:** `minecraft-data/data/pc/1.7/protocol.json`,
`minecraft-data/data/pc/1.19/protocol.json`,
`minecraft-data/data/pc/1.19.2/protocol.json`,
`minecraft-data/data/pc/1.20.2/protocol.json`;
`BungeeCord/protocol/src/main/java/net/md_5/bungee/protocol/packet/LoginRequest.java:2839`
Version notes:
- **≤1.19.2:** `Signature` container present (profile public key for chat-signing); removed in 1.19.3.
- **1.19:** only `signature` field (no UUID).
- **1.19.2:** adds optional `playerUUID` after the signature.
- **1.19.11.20.1:** UUID is optional (preceded by a boolean flag).
- **1.20.2+:** UUID is unconditionally included (no flag byte).
(`BungeeCord LoginRequest.java:3538`: `if (protocolVersion >= MINECRAFT_1_20_2)` reads UUID directly.)
---
### 2.2 Encryption Request (0x01 Clientbound)
Sent by the server in online mode to initiate the key exchange.
| Field | Type | Version range | Notes |
|---|---|---|---|
| Server ID | String(20) | all | Historically a 10-char hex; **empty string `""` in vanilla 1.7+** |
| Public Key | Prefixed byte array | 1.8+ | DER-encoded X.509 SubjectPublicKeyInfo of server's RSA-1024 public key |
| Public Key | 2-byte-length byte array | 1.7 (pre-Netty) | Same key, length prefixed with `i16` |
| Verify Token | Prefixed byte array | all | 4 random bytes generated per-connection |
| Should Authenticate | Boolean | 1.20.5+ | Whether Mojang session auth should be performed |
**Source:** `Velocity/proxy/.../EncryptionRequestPacket.java:6274` (1.7 vs 1.8+ branching on
`readByteArray` vs `readByteArray17`; `shouldAuthenticate` field read at line 6769);
`BungeeCord/protocol/.../EncryptionRequest.java:2630`;
`minecraft-data/data/pc/1.7/protocol.json` (countType `i16`);
`minecraft-data/data/pc/1.8/protocol.json` (countType `varint`).
Notes:
- The Server ID string was meaningful in old Alpha/Beta versions but **has been an empty
string `""` in all vanilla servers since 1.7** (wiki.minecraft.net/w/Java_Edition_protocol/Encryption).
Some implementations (node-minecraft-protocol `server/login.js:89`) generate a short random
hex string instead.
- `shouldAuthenticate` (1.20.5+) lets the server tell the client not to call the Mojang
session endpoint; used for offline-mode negotiation without breaking the encryption handshake.
(`Velocity/proxy/.../EncryptionRequestPacket.java:6769`)
---
### 2.3 Encryption Response (0x01 Serverbound)
Client reply containing the RSA-encrypted shared secret and token.
**Standard form (all versions except 1.191.19.2 with profile keys):**
| Field | Type | Notes |
|---|---|---|
| Shared Secret | Prefixed byte array | 128 bytes (RSA-PKCS1 v1.5 ciphertext of 16-byte secret) |
| Verify Token | Prefixed byte array | 128 bytes (RSA-PKCS1 v1.5 ciphertext of the 4-byte token) |
**1.191.19.2 form with profile keys (chat signing enabled):**
| Field | Type | Notes |
|---|---|---|
| Shared Secret | Prefixed byte array | 128 bytes as above |
| Has Verify Token | Boolean | `true` = standard token; `false` = signed salt |
| — if true: Verify Token | Prefixed byte array | 128 bytes as above |
| — if false: Salt | i64 | Random 64-bit salt |
| — if false: Message Signature | Prefixed byte array | SHA256withRSA sig over `verifyToken ‖ salt` |
**Source:** `Velocity/proxy/.../EncryptionResponsePacket.java:66101` (version branching);
`BungeeCord/protocol/.../EncryptionResponse.java:2455`;
`node-minecraft-protocol/src/client/encrypt.js:5273` (1.19 `hasVerifyToken`/`salt`/`messageSignature` branch);
`node-minecraft-protocol/src/server/login.js:109141` (server-side verification).
Notes:
- `Has Verify Token = false` only occurs in 1.191.19.2 when the client has a profile key
(chat-signing enabled). The server verifies the salt+signature against the player's
profile public key from Login Start instead of decrypting the token.
(`server/login.js:120126`: `crypto.verify('sha256WithRSAEncryption', …)`)
- In 1.19.3+, profile keys were removed from Login Start; `Has Verify Token` field is gone
and the packet reverts to the simple two-field form.
(`Velocity/proxy/.../EncryptionResponsePacket.java:7074`: version range `>= 1.19 && < 1.19.3`)
---
### 2.4 Set Compression (0x03 Clientbound)
Optional packet. If sent, all subsequent packets in Login (and Play) use the compressed
framing described in §4.
| Field | Type | Notes |
|---|---|---|
| Threshold | VarInt | Minimum uncompressed size to trigger compression. Negative = disable. |
**Source:** `BungeeCord/protocol/.../SetCompression.java:1922`;
`node-minecraft-protocol/src/server/login.js:179181` (`client.write('compress', { threshold: 256 })`).
Notes:
- Added in **1.8** (protocol version 47, snapshot 14w28a).
(`node-minecraft-protocol/src/server/login.js:179`: `if (client.protocolVersion >= 27)`)
- Vanilla default threshold is **256 bytes** uncompressed payload.
- A threshold of `-1` disables compression; packets keep the uncompressed framing.
- **Must be sent before Login Success** if used. The server enables its own compressor and
decompressor as soon as it sends this packet; the client enables them on receipt.
---
### 2.5 Login Success (0x02 Clientbound)
Signals the end of authentication. Carries the player's resolved UUID and username.
| Field | Type | Version range | Notes |
|---|---|---|---|
| UUID | String (hyphenated) | ≤1.15 | `"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"` |
| UUID | UUID (128-bit wire) | 1.16+ | Raw 16-byte big-endian UUID |
| Username | String(16) | all | Canonical (case-corrected) name from Mojang |
| Properties | Array of Property | 1.19+ | Texture/cape data; each has `name`, `value`, optional `signature` |
| Strict Error Handling | Boolean | 1.20.51.21.1 | Vanilla sends `true` |
**Source:** `BungeeCord/protocol/.../LoginSuccess.java:2843` (UUID wire-type branch at 1.16;
properties at 1.19; `strictErrorHandling` at 1.20.51.21.1);
`node-minecraft-protocol/src/server/login.js:184188`.
Notes:
- UUID comes from the Mojang session server response (`id` field, dashes stripped) in online
mode. In offline mode it is `UUID.nameUUIDFromBytes("OfflinePlayer:" + username)`.
- **Login Success is sent encrypted** (all prior negotiation including Encryption Response
triggers immediate cipher switch; see §3.5).
---
### 2.6 Login Acknowledged (0x03 Serverbound)
No fields. The client sends this to confirm it has processed Login Success and is ready to
enter the **Configuration** state. Added in **1.20.2**.
**Source:** `minecraft-data/data/pc/1.20.2/protocol.json` (toServer `0x03: login_acknowledged`);
`node-minecraft-protocol/src/server/login.js:189191`
(`if (client.supportFeature('hasConfigurationState')) client.once('login_acknowledged', …)`);
`minecraft-data/data/pc/common/features.json` (`hasConfigurationState` versions: `["1.20.2", "latest"]`).
---
## 3. Encryption Handshake
### 3.1 Sequence Diagram
```mermaid
sequenceDiagram
participant C as Client
participant S as Server
participant M as sessionserver.mojang.com
C->>S: Handshake (next_state=2)
C->>S: Login Start (username [+ profile key 1.191.19.2])
Note over S: online-mode? generate RSA-1024 keypair (once at startup)
S->>C: Encryption Request (serverId="", DER pubkey, 4-byte verifyToken)
Note over C: generate 16-byte random sharedSecret
Note over C: RSA-PKCS1-v1.5 encrypt sharedSecret → enc_secret (128 B)
Note over C: RSA-PKCS1-v1.5 encrypt verifyToken → enc_token (128 B)
Note over C: compute serverIdHash = MinecraftSHA1(serverId‖sharedSecret‖pubKey)
C->>M: POST /session/minecraft/join {accessToken, selectedProfile, serverId=serverIdHash}
M-->>C: 204 No Content
C->>S: Encryption Response (enc_secret, enc_token)
Note over C: enable AES/CFB8 (key=sharedSecret, IV=sharedSecret) — all outgoing encrypted now
Note over S: RSA-decrypt enc_secret → sharedSecret
Note over S: RSA-decrypt enc_token → verify == original verifyToken?
Note over S: compute serverIdHash = MinecraftSHA1(serverId‖sharedSecret‖pubKey)
S->>M: GET /session/minecraft/hasJoined?username=&serverId=serverIdHash
M-->>S: 200 {id, name, properties} or 204 (not found)
Note over S: enable AES/CFB8 (key=sharedSecret, IV=sharedSecret) — all outgoing encrypted now
S->>C: [Set Compression] (optional, 1.8+)
S->>C: Login Success (uuid, username, properties) — encrypted
C->>S: Login Acknowledged (1.20.2+) — encrypted
```
---
### 3.2 RSA Key Exchange
**Key size:** 1024-bit RSA, generated once at server startup.
- BungeeCord: `EncryptionUtil.java:51``generator.initialize(1024)`
- Velocity: `VelocityServer.java:255``EncryptionUtils.createRsaKeyPair(1024)`
**Public key encoding:** The bytes sent in Encryption Request are the **DER-encoded
X.509 SubjectPublicKeyInfo** (i.e. `java.security.PublicKey.getEncoded()` using the
`X509EncodedKeySpec`). This wraps the raw RSA key in an ASN.1 structure identifying
the algorithm (`rsaEncryption OID 1.2.840.113549.1.1.1`).
The client reconstructs the public key from this DER blob:
(`node-minecraft-protocol/src/client/encrypt.js:4850`)
```js
// encrypt.js:4850
const pubKey = mcPubKeyToPem(packet.publicKey) // DER → PEM wrapper
const encryptedSharedSecretBuffer =
crypto.publicEncrypt({ key: pubKey, padding: crypto.constants.RSA_PKCS1_PADDING }, sharedSecret)
const encryptedVerifyTokenBuffer =
crypto.publicEncrypt({ key: pubKey, padding: crypto.constants.RSA_PKCS1_PADDING }, packet.verifyToken)
```
**Padding:** PKCS#1 v1.5 (`RSA_PKCS1_PADDING` / `RSA/ECB/PKCS1Padding`).
- BungeeCord: `EncryptionUtil.java:144148``Cipher.getInstance("RSA/ECB/PKCS1Padding")`
- Velocity: `EncryptionUtils.java:190193``Cipher.getInstance("RSA")` (JCA default = PKCS#1 v1.5)
**Ciphertext size:** With a 1024-bit key and PKCS#1 v1.5 padding, any input ≤ 117 bytes
produces a **128-byte ciphertext**. Both the encrypted shared secret and encrypted verify
token are therefore 128 bytes on the wire.
---
### 3.3 Server-ID Hash Algorithm
The **serverIdHash** (called `serverId` in the Mojang API) is a non-standard SHA-1 digest
formatted as a signed two's-complement hex integer, possibly with a leading `-`.
**Inputs (in order):**
1. `serverId` string bytes, encoded as ISO-8859-1 (Latin-1)
— in vanilla this is always an empty string, so no bytes are contributed.
2. `sharedSecret` — the raw 16-byte AES key.
3. Server's RSA public key — DER-encoded SubjectPublicKeyInfo bytes.
**Algorithm:**
```python
import hashlib, textwrap
def minecraft_sha1_hex(server_id: str, shared_secret: bytes, server_pub_der: bytes) -> str:
h = hashlib.sha1()
h.update(server_id.encode('iso-8859-1'))
h.update(shared_secret)
h.update(server_pub_der)
digest = h.digest() # 20 raw bytes
# Interpret as a signed big-endian integer (two's complement):
n = int.from_bytes(digest, byteorder='big', signed=True)
return format(n, 'x') # hex, with '-' prefix if negative
```
Java equivalent (BungeeCord `InitialHandler.java:517525`):
```java
MessageDigest sha = MessageDigest.getInstance("SHA-1");
sha.update(request.getServerId().getBytes("ISO_8859_1"));
sha.update(sharedKey.getEncoded());
sha.update(EncryptionUtil.keys.getPublic().getEncoded());
String hash = new BigInteger(sha.digest()).toString(16);
// BigInteger(byte[]) treats the input as two's-complement big-endian → negative if MSB set
```
Velocity (`EncryptionUtils.java:203212`) omits the `serverId` update because the
field is always `""` in Velocity:
```java
digest.update(sharedSecret); // EncryptionUtils.java:207
digest.update(key.getEncoded()); // EncryptionUtils.java:208
return twosComplementHexdigest(digest.digest());
// twosComplementHexdigest: new BigInteger(digest).toString(16) (line 179)
```
**Wiki examples** (minecraft.wiki/w/Java_Edition_protocol/Encryption):
| Input string | serverIdHash |
|---|---|
| `Notch` | `4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48` |
| `jeb_` | `-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1` |
| `simon` | `88e16a1019277b15d58faf0541e11910eb756f6` |
Note: these examples use the string as the entire input (simulating a server where
`serverId = "Notch"` etc.), not a realistic scenario.
**Why two's complement?** The SHA-1 output is 20 bytes. Mojang's original Minecraft code
treated this raw byte array as a signed big-endian integer via Java's `BigInteger(byte[])`.
If the high bit of the first byte is set (digest[0] ≥ 0x80), the integer is negative, and
the hex string gets a leading `-`. This is non-standard but has been the canonical format
since Minecraft Beta 1.2.
---
### 3.4 AES/CFB8 Stream Cipher
After both sides process Encryption Response, every subsequent byte on the TCP connection
is encrypted. There are **no unencrypted bytes** after this point — not even the packet
length VarInt.
**Cipher parameters:**
| Parameter | Value |
|---|---|
| Algorithm | AES |
| Mode | CFB8 (Cipher Feedback, 8-bit segment size) |
| Key | 16-byte shared secret |
| IV | 16-byte shared secret (same as key) |
| Padding | None |
BungeeCord (`JavaCipher.java`):
```java
this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
cipher.init(mode, key, new IvParameterSpec(key.getEncoded()));
// key.getEncoded() == the 16-byte shared secret, used as IV
```
Velocity (`JavaVelocityCipher.java:5061`):
```java
this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
this.cipher.init(
encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE,
key,
new IvParameterSpec(key.getEncoded()) // IV = key = shared secret
);
```
node-minecraft-protocol (`transforms/encryption.js:617`):
```js
// encryption.js:69
function createCipher(secret) {
if (crypto.getCiphers().includes('aes-128-cfb8')) {
return crypto.createCipheriv('aes-128-cfb8', secret, secret) // key=secret, IV=secret
}
return new Cipher(secret) // fallback uses aes-js CFB with segment size 1 byte
}
```
**Important:** Reusing the key as the IV is a known weakness (noted in Velocity source,
`JavaVelocityCipher.java:5258`: *"reusing the key as the IV defeats the entire point"*).
The cipher is initialized this way by Mojang design; it cannot be changed without a
protocol breaking change.
**Continuous operation:** The AES/CFB8 state is maintained across packets. The cipher is
never reset or re-initialized between packets; the feedback register carries over.
---
### 3.5 Timing: When Encryption Activates
- **Client:** enables AES/CFB8 immediately after *sending* Encryption Response
(`node-minecraft-protocol/src/client/encrypt.js:73`: `client.setEncryption(sharedSecret)`)
- **Server:** enables AES/CFB8 after *receiving* Encryption Response and verifying it
(`node-minecraft-protocol/src/server/login.js:150`: `client.setEncryption(sharedSecret)`;
Velocity `InitialLoginSessionHandler.java:234`: `mcConnection.enableEncryption(decryptedSharedSecret)`)
The server enables encryption before calling the Mojang session server, so if `hasJoined`
fails, the server sends a disconnect packet that is already encrypted.
---
## 4. Session Authentication (Online Mode)
### 4.1 Client → Mojang: `join`
Before sending Encryption Response, the client posts to the Mojang session server:
```
POST https://sessionserver.mojang.com/session/minecraft/join
Content-Type: application/json
{
"accessToken": "<launcher access token>",
"selectedProfile": "<UUID of player, dashes removed>",
"serverId": "<serverIdHash>"
}
```
Expected response: **204 No Content** on success.
(`node-minecraft-protocol/src/client/encrypt.js:4143`: `yggdrasilServer.join(accessToken, selectedProfile.id, packet.serverId, sharedSecret, packet.publicKey, cb)` — the `join` call in the yggdrasil library computes the hash internally from `serverId + sharedSecret + publicKey`.)
### 4.2 Server → Mojang: `hasJoined`
After enabling encryption and verifying the token, the server queries:
```
GET https://sessionserver.mojang.com/session/minecraft/hasJoined
?username=<username>
&serverId=<serverIdHash>
[&ip=<client_ip>] # optional; prevents proxy-hopping
```
- **200 OK** → body is a GameProfile JSON `{id, name, properties[]}`. Login proceeds.
- **204 No Content** → client did not call `join`; kick with `"offline_mode_player"` or similar.
(`Velocity/proxy/.../InitialLoginSessionHandler.java:6871`: URL template;
BungeeCord `InitialHandler.java:527528`: URL construction with `URLEncoder.encode`.)
### 4.3 Mojang → Microsoft Account Migration
The original `sessionserver.mojang.com` endpoints still work after the Mojang → Microsoft
account migration. All Minecraft clients (Bedrock and Java launcher) now use Microsoft OAuth
tokens internally, but the session server interface is unchanged at the API level.
<!-- VERIFY: confirm current sessionserver.mojang.com still accepts calls in 2025/2026 -->
### 4.4 Offline Mode
In offline mode, the server skips:
- Sending Encryption Request (no encryption handshake at all)
- Calling `hasJoined`
UUID is derived deterministically: `UUID.nameUUIDFromBytes("OfflinePlayer:" + username)` (MD5-based
UUID v3 equivalent). (`BungeeCord/proxy/.../connection/InitialHandler.java:560`)
---
## 5. 1.19 Profile Public Keys (Chat Signing)
Minecraft 1.19 introduced per-player RSA key pairs signed by Mojang, allowing chat message
signing. The **Login Start** packet gained a `Signature` field to deliver the player's public
key to the server.
This only affects the **login phase** through two mechanisms:
1. **Login Start (1.191.19.2):** Client sends its profile public key (DER + Mojang signature).
2. **Encryption Response (1.191.19.2):** Client may replace the encrypted verify token with
a salt + signature over `verifyToken ‖ salt`, proving possession of the private key
corresponding to the profile key.
### Profile Key Container (in Login Start, 1.191.19.2)
| Sub-field | Type | Notes |
|---|---|---|
| Timestamp | i64 | Key expiry in milliseconds since epoch |
| Public Key | Prefixed byte array | DER SubjectPublicKeyInfo |
| Signature | Prefixed byte array | Mojang signature over the key material |
**Signature verification (server side):**
- **1.19:** verify against Mojang's public key over `UTF8(expiryTimestamp + PEM(playerPubKey))`.
(`node-minecraft-protocol/src/server/login.js:72`: `Buffer.from(timestamp + mcPubKeyToPem(publicKey), 'utf8')`)
- **1.19.2 (profileKeySignatureV2):** verify against Mojang's public key over
`UUID(playerUUID) ‖ i64(timestamp) ‖ DER(playerPubKey)`.
(`node-minecraft-protocol/src/server/login.js:71`:
`concat('UUID', playerUUID, 'i64', timestamp, 'buffer', publicKey.export({type:'spki', format:'der'}))`)
- Both use **RSA-SHA1** (`crypto.verify('RSA-SHA1', …)`).
(`minecraft-data/data/pc/common/features.json`: `signatureEncryption` active on `["1.19", "1.19.2"]`;
`profileKeySignatureV2` active on `["1.19.2", "latest"]`.)
### Removal in 1.19.3
Profile keys were removed from Login Start in 1.19.3. The `Signature` field is absent, the
`Has Verify Token` field in Encryption Response is also gone, and the standard two-field
Encryption Response format is used again.
(`Velocity/proxy/.../EncryptionResponsePacket.java:7074`:
`if (version.noLessThan(MINECRAFT_1_19) && version.lessThan(MINECRAFT_1_19_3)) { salt check }`)
---
## 6. Compression
### 6.1 Negotiation
The server may send **Set Compression** (0x03 clientbound) at any point during the Login
state, before Login Success. The threshold value (VarInt) controls when zlib is applied:
- **threshold < 0** — compression is disabled (revert to uncompressed framing).
- **threshold = 0** — compress all packets.
- **threshold > 0** (common: 256) — compress only packets whose uncompressed data length
exceeds the threshold.
Compression was added in **1.8** (snapshot 14w28a, protocol version 47).
(`node-minecraft-protocol/src/server/login.js:179`: `if (client.protocolVersion >= 27)`)
### 6.2 Packet Format Before Set Compression
```
┌─────────────────────────────────────────────────────────┐
│ Packet Length (VarInt) — byte count of ID + Data │
│ Packet ID (VarInt) │
│ Data (bytes) │
└─────────────────────────────────────────────────────────┘
```
### 6.3 Packet Format After Set Compression
Two VarInts precede the payload:
```
┌─────────────────────────────────────────────────────────┐
│ Packet Length (VarInt) — byte count of (Data Length │
│ VarInt + possibly compressed │
│ ID+Data) │
│ Data Length (VarInt) — 0 = not compressed; │
│ >0 = uncompressed byte count │
│ Packet ID + Data (bytes) — zlib-compressed if DL>0, │
│ raw otherwise │
└─────────────────────────────────────────────────────────┘
```
**Uncompressed case** (data length < threshold):
- Data Length VarInt = **0**
- ID + Data follow uncompressed
**Compressed case** (data length ≥ threshold):
- Data Length VarInt = uncompressed byte count of ID+Data
- ID + Data bytes are zlib (`DEFLATE`) compressed
BungeeCord implementation (`netty/LengthPrependerAndCompressor.java:4793`):
```java
// uncompressed path (line 5463):
if (oldBodyLen < threshold) {
DefinedPacket.writeVarInt(oldBodyLen + 1, lenBuf); // Packet Length
lenBuf.writeByte(0); // Data Length = 0 → uncompressed
// ... append raw body ...
}
// compressed path (line 7893):
else {
DefinedPacket.writeVarInt(oldBodyLen, buf); // Data Length = original size
zlib.process(msg, buf); // compress
// Packet Length written as VarInt in front
}
```
### 6.4 Interaction with Encryption
Compression and encryption are independent layers. When both are active:
1. **Write path:** `Compress → Encrypt → TCP`
2. **Read path:** `TCP → Decrypt → Decompress`
The encrypted bytestream carries compressed packet data; the AES/CFB8 cipher sees
compressed bytes and does not know about packet boundaries.
---
## 7. Version Summary
| Feature | Introduced | Removed / Changed |
|---|---|---|
| Login Start: basic (name only) | 1.7 | — |
| Encryption request/response | 1.2.5 (Beta) | — |
| Byte arrays length-prefixed with VarInt | 1.8 | — |
| Set Compression packet | 1.8 (14w28a) | — |
| Login Start: profile public key | 1.19 | Removed 1.19.3 |
| Encryption Response: Has Verify Token + salt | 1.19 | Removed 1.19.3 |
| Login Start: optional UUID | 1.19.1 | Mandatory 1.20.2+ |
| Profile key signature V2 | 1.19.2 | — |
| Login Acknowledged packet | 1.20.2 | — |
| Configuration state after login | 1.20.2 | — |
| Encryption Request: Should Authenticate | 1.20.5 | — |
| Login Success: Strict Error Handling | 1.20.5 | Removed 1.21.2 |
| Login Success: UUID as raw 128-bit wire type | 1.16 | — |
| Login Success: Properties array | 1.19 | — |
---
## 8. Sources
| Reference | Path |
|---|---|
| node-minecraft-protocol encrypt.js | `node-minecraft-protocol/src/client/encrypt.js` |
| node-minecraft-protocol encryption.js | `node-minecraft-protocol/src/transforms/encryption.js` |
| node-minecraft-protocol server/login.js | `node-minecraft-protocol/src/server/login.js` |
| Velocity EncryptionRequestPacket | `Velocity/proxy/.../packet/EncryptionRequestPacket.java` |
| Velocity EncryptionResponsePacket | `Velocity/proxy/.../packet/EncryptionResponsePacket.java` |
| Velocity EncryptionUtils | `Velocity/proxy/.../crypto/EncryptionUtils.java` |
| Velocity JavaVelocityCipher | `Velocity/native/.../encryption/JavaVelocityCipher.java` |
| Velocity InitialLoginSessionHandler | `Velocity/proxy/.../client/InitialLoginSessionHandler.java` |
| BungeeCord EncryptionUtil | `BungeeCord/proxy/.../EncryptionUtil.java` |
| BungeeCord InitialHandler | `BungeeCord/proxy/.../connection/InitialHandler.java` |
| BungeeCord JavaCipher | `BungeeCord/native/.../cipher/JavaCipher.java` |
| BungeeCord LengthPrependerAndCompressor | `BungeeCord/proxy/.../netty/LengthPrependerAndCompressor.java` |
| BungeeCord LoginRequest | `BungeeCord/protocol/.../packet/LoginRequest.java` |
| BungeeCord LoginSuccess | `BungeeCord/protocol/.../packet/LoginSuccess.java` |
| BungeeCord EncryptionRequest | `BungeeCord/protocol/.../packet/EncryptionRequest.java` |
| BungeeCord EncryptionResponse | `BungeeCord/protocol/.../packet/EncryptionResponse.java` |
| BungeeCord SetCompression | `BungeeCord/protocol/.../packet/SetCompression.java` |
| minecraft-data protocol.json (all versions) | `minecraft-data/data/pc/<version>/protocol.json` |
| minecraft-data features.json | `minecraft-data/data/pc/common/features.json` |
| minecraft.wiki Protocol Encryption | `https://minecraft.wiki/w/Java_Edition_protocol/Encryption` |
<!-- VERIFY: sessionserver.mojang.com/session/minecraft/join and hasJoined still live as of 2026 — Microsoft migration kept these endpoints but confirm they remain unredirected. -->
<!-- VERIFY: RSA-1024 key size — BungeeCord and Velocity both confirmed at 1024 bits; check if vanilla Minecraft server (NMS) uses a different size. -->
<!-- VERIFY: Login Start UUID presence in 1.19.1 exact behaviour — BungeeCord LoginRequest.java:3338 shows optional boolean + UUID; verify the flag is `buf.readBoolean()` not some other encoding in vanilla packets. -->