jackc/pgx · critical

invalid SCRAM ServerSignature received from server

Error message

invalid SCRAM ServerSignature received from server

What it means

Returned by recvServerFinalMessage when the server's 'v=' ServerSignature does not match the HMAC the client computes from the derived ServerKey. SCRAM uses this to let the CLIENT authenticate the SERVER: a mismatch means the server does not hold the credentials corresponding to this password. Against genuine PostgreSQL this strongly suggests a man-in-the-middle that cannot compute the valid signature.

Source

Thrown at pgconn/auth_scram.go:341

	if err != nil {
		panic(err) // This should never happen.
	}
	sc.authMessage = bytes.Join([][]byte{sc.clientFirstMessageBare, sc.serverFirstMessage, clientFinalMessageWithoutProof}, []byte(","))

	clientProof := computeClientProof(sc.saltedPassword, sc.authMessage)

	return fmt.Sprintf("%s,p=%s", clientFinalMessageWithoutProof, clientProof)
}

func (sc *scramClient) recvServerFinalMessage(serverFinalMessage []byte) error {
	if !bytes.HasPrefix(serverFinalMessage, []byte("v=")) {
		return errors.New("invalid SCRAM server-final-message received from server")
	}

	serverSignature := serverFinalMessage[2:]

	if !hmac.Equal(serverSignature, computeServerSignature(sc.saltedPassword, sc.authMessage)) {
		return errors.New("invalid SCRAM ServerSignature received from server")
	}

	return nil
}

func computeHMAC(key, msg []byte) []byte {
	mac := hmac.New(sha256.New, key)
	mac.Write(msg)
	return mac.Sum(nil)
}

func computeClientProof(saltedPassword, authMessage []byte) []byte {
	clientKey := computeHMAC(saltedPassword, []byte("Client Key"))
	storedKey := sha256.Sum256(clientKey)
	clientSignature := computeHMAC(storedKey[:], authMessage)

	clientProof := make([]byte, len(clientSignature))
	for i := range clientSignature {

View on GitHub (pinned to ec1a0befd2)

Solutions

  1. Use sslmode=verify-full with a correct sslrootcert so the server identity is authenticated, preventing MITM.
  2. Remove any TLS-terminating proxy or ensure it does not reoriginate the connection.
  3. Investigate as a potential MITM/security incident; verify the server certificate chain and hostname.

Example fix

// before: vulnerable to MITM
connStr := "host=db user=app sslmode=require"

// after: full verification prevents the signature mismatch
connStr := "host=db user=app sslmode=verify-full sslrootcert=/etc/ssl/db-ca.pem"
Defensive patterns

Strategy: try-catch

Try / catch

// ServerSignature mismatch means the server could not prove it holds the credentials.
if err := connect(); err != nil && strings.Contains(err.Error(), "invalid SCRAM ServerSignature") {
    securityLog.Error("SCRAM server signature mismatch: likely MITM on an unverified TLS channel", "err", err)
    return errors.Join(err, errPossibleMITM)
}

Prevention

When it happens

Trigger: After sending the client-final-message, the server's returned signature fails hmac.Equal against computeServerSignature(saltedPassword, authMessage). Happens when a MITM relayed the handshake but cannot recompute the proof, or when the exchange was tampered with after the client proof was computed.

Common situations: TLS with InsecureSkipVerify (sslmode=prefer/require without root cert) allowing a MITM; a rogue proxy presenting as the DB; extremely rarely, memory corruption. Note: a WRONG password yields a server ErrorResponse, not this error.

Related errors


AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04). Data as JSON: /data/errors/8d9e0f00a1f17982.json. Report an issue: GitHub.