Files
minecraft_protocol/05-login-encryption.md
T
claude-timemachine d73c1c9537 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>
2026-06-19 14:15:32 +02:00

620 lines
27 KiB
Markdown
Raw 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.
# 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. -->