XTLS/Xray-core · error
decrypt verify token: %w
Error message
decrypt verify token: %w
What it means
RSA PKCS#1 v1.5 decryption of the encrypted verify token failed. The verify token is the 4 random bytes the server sent in its encryption request, echoed back encrypted with the server public key (in this fork it also carries the password suffix). Decryption failure means the ciphertext does not match the server's private key or is malformed.
Source
Thrown at transport/internet/finalmask/xmc/server.go:199
return fmt.Errorf("bad encrypt response packet id")
}
err = pkt.readFields(&encryptedSharedSecret, &encryptedVerifyToken)
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:]View on GitHub (pinned to 7d214f8b09)
Solutions
- Compare len(encryptedVerifyToken) with the RSA modulus size — an off-by-framing truncation shows up immediately.
- Confirm the client encrypts both fields with the exact public key bytes from the encryption request packet of THIS session.
- Treat as a failed authentication: disconnect and count the event; repeated hits from one IP indicate probing.
- If both this and error 961 fire together, suspect key mismatch; if only this fires, suspect field framing.
Example fix
// before
decryptedVerifyToken, err = rsa.DecryptPKCS1v15(rand.Reader, c.rsaPrivateKey, encryptedVerifyToken)
if err != nil {
return fmt.Errorf("decrypt verify token: %w", err)
}
// after: add size context for triage
decryptedVerifyToken, err := rsa.DecryptPKCS1v15(rand.Reader, c.rsaPrivateKey, encryptedVerifyToken)
if err != nil {
return fmt.Errorf("decrypt verify token (ct len %d): %w", len(encryptedVerifyToken), err)
} Defensive patterns
Strategy: validation
Validate before calling
if len(encryptedVerifyToken) != c.rsaPrivateKey.Size() {
return fmt.Errorf("verify token ciphertext has wrong size %d", len(encryptedVerifyToken))
} Try / catch
if _, err := rsa.DecryptPKCS1v15(rand.Reader, key, encToken); err != nil {
return errors.New("verify token rejected") // generic message, no crypto detail
} Prevention
- Client must encrypt token+password as one RSA block with the session's exact public key.
- Check both ciphertext lengths before decrypting either; cheap and localizes failures.
- Rate-limit repeated handshake failures per source address.
When it happens
Trigger: Client encrypted the token with a different public key than the one in this connection's encryption request; the encryptedVerifyToken field was truncated or empty from a framing bug; hostile random bytes. Note the shared secret decrypted fine at this point, so a failure here specifically implicates the second field or a partially-replayed handshake.
Common situations: Handshake replay where an attacker reuses an old shared-secret ciphertext but a fresh garbage token; client library bug serializing two RSA blocks; key rotation races.
Related errors
- decrypt shared secret: %w
- auth method not supported.
- derive minecraft rsa key: %w
- parse server public key: %w
- parse server public key: not rsa
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/f05dbb9eedd72cfb.
Report an issue: GitHub.