XTLS/Xray-core · error
verify token mismatch
Error message
verify token mismatch
What it means
The decrypted verify token's first 4 bytes do not equal the random token the server generated for this handshake (or the token is shorter than 4 bytes). This is the anti-replay, session-binding check of the Minecraft encryption handshake: a legitimate client echoes the exact bytes back. A mismatch means the response belongs to a different session, was replayed, or the client is broken/hostile.
Source
Thrown at transport/internet/finalmask/xmc/server.go:203
if err != nil {
return fmt.Errorf("read encrypt response: %w", err)
}
sharedSecret, err = rsa.DecryptPKCS1v15(rand.Reader, c.rsaPrivateKey, encryptedSharedSecret)
if err != nil {
return fmt.Errorf("decrypt shared secret: %w", err)
}
if len(sharedSecret) != 16 {
return fmt.Errorf("bad shared secret length: %d", len(sharedSecret))
}
decryptedVerifyToken, err = rsa.DecryptPKCS1v15(rand.Reader, c.rsaPrivateKey, encryptedVerifyToken)
if err != nil {
return fmt.Errorf("decrypt verify token: %w", err)
}
if len(decryptedVerifyToken) < 4 || !bytes.Equal(verifyToken, decryptedVerifyToken[:4]) {
return fmt.Errorf("verify token mismatch")
}
c.reader, err = newCryptoReader(c.reader, sharedSecret)
if err != nil {
return fmt.Errorf("new crypto reader: %w", err)
}
c.writer, err = newCryptoWriter(c.writer, sharedSecret)
if err != nil {
return fmt.Errorf("new crypto writer: %w", err)
}
// verify password
receivedPassword := decryptedVerifyToken[4:]
if subtle.ConstantTimeCompare(receivedPassword, []byte(c.password)) != 1 {
writeDisconnectPacket(c.writer, `{"type":"translatable","translate":"multiplayer.disconnect.authservers_down"}`)
return fmt.Errorf("bad password")View on GitHub (pinned to 7d214f8b09)
Solutions
- Confirm verifyToken is generated and stored per connection (the code does rand.Read per handshake — verify no refactor made it shared).
- If writing a client, echo the token bytes verbatim: encrypt exactly the 4 bytes received plus the password, nothing more.
- Disconnect on mismatch — retrying with the same token would defeat the check's purpose.
- Log the expected and received bytes at trace level during client development to pinpoint echo bugs.
Example fix
// before
if len(decryptedVerifyToken) < 4 || !bytes.Equal(verifyToken, decryptedVerifyToken[:4]) {
return fmt.Errorf("verify token mismatch")
}
// after: distinguish the two failure shapes for clearer logs
if len(decryptedVerifyToken) < 4 {
return fmt.Errorf("verify token too short: %d bytes", len(decryptedVerifyToken))
}
if !bytes.Equal(verifyToken, decryptedVerifyToken[:4]) {
return fmt.Errorf("verify token mismatch")
} Defensive patterns
Strategy: validation
Validate before calling
if len(decryptedVerifyToken) < 4 {
return errors.New("verify token too short")
}
if !bytes.Equal(verifyToken, decryptedVerifyToken[:4]) {
return errors.New("verify token mismatch")
} Prevention
- Generate verifyToken fresh per connection with crypto/rand; never share it across sessions.
- Client: echo the 4 token bytes verbatim before appending the password.
- Treat mismatches as replay attempts and count them per IP for abuse detection.
When it happens
Trigger: Replaying a captured encryption-response packet from an earlier session; a client that echoes the wrong token bytes (endian/encoding bug); concurrency bug where two connections' tokens got crossed (e.g. shared state instead of per-connection verifyToken); decryptedVerifyToken shorter than 4 bytes.
Common situations: Attack tooling that records and replays handshakes; parallel connection tests where token state leaks between connections; client implementation that hashes or transforms the token instead of echoing it.
Related errors
- bad shared secret length: %d
- decrypt verify token: %w
- invalid token + token
- Failed to build REALITY config.
- socks 4 is not allowed when auth is required.
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/67b4324982a5b7a9.
Report an issue: GitHub.