Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

sql.postgres.auth

import sql.postgres.auth

sql does not re-export this module, so it is reached only by importing it directly.

The authentication exchanges a PostgreSQL server can ask for.

Which one happens is the server’s choice, made from its pg_hba.conf. A modern server asks for SCRAM-SHA-256; older ones ask for MD5, which PostgreSQL itself now discourages; and a server set to trust asks for nothing at all.

Constants

SCRAM_SHA_256

sql.postgres.auth.SCRAM_SHA_256 = 'SCRAM-SHA-256'

The mechanism this adapter implements. Channel binding is not offered, so the server sees plain SCRAM-SHA-256 rather than the -PLUS variant.

Functions

md5_response()

sql.postgres.auth.md5_response(user: string, password: string, salt) -> string

Builds the response to an MD5 password request.

The scheme is md5(md5(password + user) + salt), hex encoded, with a literal md5 in front. It is weak and PostgreSQL says so; it is here because servers still ask for it.

Parameters

  • user (string)
  • password (string)
  • salt (bytes) — The four bytes the server sent.

Returns string

nonce()

sql.postgres.auth.nonce() -> string

A fresh SCRAM nonce.

It has to be unpredictable: the nonce is what stops a recorded exchange being replayed, so it comes from the system’s random source rather than from rand().

Returns string

parse_scram()

sql.postgres.auth.parse_scram(message: string) -> dict

Splits a SCRAM message into its key=value parts.

Parameters

  • message (string)

Returns dict

offers_scram()

sql.postgres.auth.offers_scram(payload) -> bool

Whether the server offered SCRAM-SHA-256 among its mechanisms.

Parameters

  • payload (bytes) — The mechanism list, zero-terminated names.

Returns bool

client_first()

sql.postgres.auth.client_first(client_nonce: string) -> dict

The client’s opening SCRAM message.

The username is left empty, which is what PostgreSQL expects: it takes the user from the startup message and ignores this one.

Parameters

  • client_nonce (string)

Returns dict — { bare, full }, the second with the channel binding header the exchange is signed over.

client_proof()

sql.postgres.auth.client_proof(password: string, client_nonce: string, client_first_bare: string, server_first: string) -> dict

Works out the client’s proof from the server’s challenge.

This is SCRAM as RFC 5802 defines it: the password is salted and iterated into a salted password, which yields a client key, whose hash the server already holds. Signing the whole exchange with that hash and returning the signature XORed with the client key proves the password without sending it.

Parameters

  • password (string)
  • client_nonce (string) — The nonce sent in the first message.
  • client_first_bare (string) — The first message without its header.
  • server_first (string) — The server’s reply, verbatim.

Returns dict — { message, server_signature }, the message to send and the signature the server’s own reply has to match.

Raises AuthenticationError if the server’s reply is malformed or its nonce does not extend the client’s.

verify_server()

sql.postgres.auth.verify_server(server_final: string, expected: string)

Checks the server’s closing message really is from a server that knows the password.

Skipping this would leave the exchange one-way, proving the client to the server but not the server to the client.

Parameters

  • server_final (string)
  • expected (string) — From client_proof().

Raises AuthenticationError if the signature is absent or wrong.


2026, Richard Ore and Zuri contributors