XTLS/Xray-core · error

bad password

Error message

bad password

What it means

Password authentication failed. This fork smuggles the password after the 4-byte verify token inside the encrypted verify-token field; after handshake success the server constant-time-compares decryptedVerifyToken[4:] against the configured c.password. On mismatch it writes a disconnect packet (mimicking Mojang's authservers_down message) and aborts the login.

Source

Thrown at transport/internet/finalmask/xmc/server.go:221

			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")
		}
		if !found {
			if err = writeDisconnectPacket(c.writer, `{"text":"You are not white-listed on this server!"}`); err != nil {
				return fmt.Errorf("write unknown login profile disconnect: %w", err)
			}
			return fmt.Errorf("unknown login profile")
		}

		loginName := String(profile.Username)
		propertyCount := Varint(1)
		propertyName := String("textures")
		texturesValue := String(profile.TexturesValue)
		signed := Boolean(true)
		texturesSignature := String(profile.TexturesSignature)
		if err = writePacket(c.writer, 0x02, &profile.UUID, &loginName, &propertyCount, &propertyName, &texturesValue, &signed, &texturesSignature); err != nil {
			return fmt.Errorf("write login finished: %w", err)
		}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Set the identical password byte-for-byte in both the server config and the client's verify-token payload; watch for trailing whitespace/newlines.
  2. If you maintain the client, verify it sends verifyToken[0:4] || password exactly, with no length prefix or padding.
  3. Compare lengths first in a log line (never log the password itself) — a 0-length received password means the client sends no suffix at all.
  4. After changing the password, restart both ends; there is no re-negotiation path mid-handshake.

Example fix

// client side, before: send only the echo token
token := verifyToken // 4 bytes
// after: append the password this server expects
token := append(append([]byte{}, verifyToken...), []byte(password)...)
encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, serverPub, token)
Defensive patterns

Strategy: validation

Validate before calling

// server startup: fail fast on empty/whitespace passwords
if strings.TrimSpace(c.password) == "" && requirePassword {
    return errors.New("xmc password must be configured")
}

Try / catch

if subtle.ConstantTimeCompare(receivedPassword, []byte(c.password)) != 1 {
    writeDisconnectPacket(c.writer, `{"type":"translatable","translate":"multiplayer.disconnect.authservers_down"}`)
    return errors.New("bad password") // already the pattern; keep the disconnect before returning
}

Prevention

When it happens

Trigger: Client concatenated the wrong password (or none) after the 4 echo bytes: wrong password configured, password empty on one side but not the other, trailing whitespace/newline in either config, or a client that only sends the bare 4-byte token so the suffix comparison fails.

Common situations: Typo or stale password after rotation in the client config; client library not aware this fork appends the password to the token; encoding differences (UTF-8 vs escaped characters); empty-password defaults differing between client and server builds.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/6523e5bdfa6a0802. Report an issue: GitHub.