{"record":{"id":"9667c795aac5d96c","repo":"XTLS/Xray-core","slug":"read-login-finished-w","errorCode":null,"errorMessage":"read login finished: %w","messagePattern":"read login finished: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"transport/internet/finalmask/xmc/client.go","lineNumber":196,"sourceCode":"\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write encryption response: %w\", err)\n\t}\n\n\t// Enable encryption\n\tc.reader, err = newCryptoReader(c.reader, sharedSecret)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"new crypto reader: %w\", err)\n\t}\n\n\tc.writer, err = newCryptoWriter(c.writer, sharedSecret)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"new crypto writer: %w\", err)\n\t}\n\n\tpkt, err = readPacket(c.reader)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"read login finished: %w\", err)\n\t}\n\tif pkt.packetID == 0x00 {\n\t\tvar reason String\n\t\tif readErr := pkt.readFields(&reason); readErr != nil {\n\t\t\treturn fmt.Errorf(\"authentication rejected\")\n\t\t}\n\t\treturn fmt.Errorf(\"authentication rejected: %s\", reason)\n\t}\n\tif pkt.packetID != 0x02 {\n\t\treturn fmt.Errorf(\"bad login finished packet id: %d\", pkt.packetID)\n\t}\n\n\treceivedProfile, err := readLoginSuccess(pkt)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"read login finished fields: %w\", err)\n\t}\n\tif receivedProfile != selectedProfile {\n\t\treturn fmt.Errorf(\"login profile mismatch\")","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/transport/internet/finalmask/xmc/client.go#L178-L214","documentation":"Reading the post-encryption 'login finished' packet through the new AES-CFB reader failed. Both sides now encrypt everything, so a read error here means the TCP read failed (EOF, reset, deadline) or the ciphertext/frames were garbage — most commonly because the server rejected the Encryption Response (wrong password or key) and dropped the connection instead of sending a disconnect packet.","triggerScenarios":"First Read/Write on the wrapped client conn: after sending 0x01 Encryption Response, readPacket on the encrypted stream errors. Typical root cause is Config.Password differing from the server's, so the server cannot decrypt the verify token and closes; also plain network loss or handshake deadline expiry.","commonSituations":"Password mismatch between client and server configs (see handshake_test.go TestHandshakePasswordMismatch), wrong RSA key pair so the server cannot recover the shared secret, or a firewall killing the session mid-handshake.","solutions":["Confirm Config.Password matches the server's password exactly (no trailing whitespace/newlines from config files)","Confirm the client's RsaPublicKey is the DER public half of the server's RsaPrivateKey","Check whether the server logged 'password mismatch'/'decrypt verify token' at the same instant","Retry with a fresh connection if the wrapped error is a transient network error"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"_, err := conn.Read(buf)\nif err != nil {\n    var ne net.Error\n    if errors.As(err, &ne) && (ne.Timeout() || strings.Contains(err.Error(), \"read encryption\")) {\n        _ = conn.Close()\n        return reconnectWithBackoff() // handshake-phase network failure is retryable\n    }\n    return err // password/key mismatches are not retryable until config is fixed\n}","preventionTips":["Diff client and server configs (password, RSA key pair) before deploying","Alert on handshake-stage failures separately from steady-state IO failures","Retry with backoff only for timeout/reset wrapped errors, never for auth rejections"],"tags":["crypto","handshake","password","network","go"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}