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
- Use sslmode=verify-full with a correct sslrootcert so the server identity is authenticated, preventing MITM.
- Remove any TLS-terminating proxy or ensure it does not reoriginate the connection.
- 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
- Always use sslmode=verify-full with the correct sslrootcert; never sslmode=require without a root cert for sensitive data.
- Eliminate TLS-terminating proxies that reoriginate the DB connection.
- Treat signature mismatches as security incidents and audit the network path.
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
- channel binding required but server does not support SCRAM-S
- channel binding required but channel binding data is not ava
- invalid SCRAM nonce: did not start with client nonce
- invalid SCRAM server-first-message received from server: did
- invalid SCRAM server-first-message received from server: did
AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04).
Data as JSON: /data/errors/8d9e0f00a1f17982.json.
Report an issue: GitHub.