PostgreSQLV · Authentication, Roles and TLSAuthentication
SCRAM, password storage, and the MD5 deprecation
What you'll learn
- Read a stored verifier and identify which algorithm produced it
- Explain why an MD5 verifier is a usable credential and a SCRAM verifier is not
- Migrate an estate from md5 to scram-sha-256 without locking anyone out
- Recognise the PostgreSQL 18 deprecation warning and act on it
Prerequisites
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
PostgreSQL does not store passwords. It stores a verifier: a value derived from the password that lets the server check a login attempt without holding the original. Two formats exist, and the operational difference between them is larger than “one is older”.
Reading a stored verifier
Verifiers live in pg_authid, which only superusers may read. The
first characters identify the algorithm.
$ psql -U postgres -c "SELECT rolname, left(rolpassword,10) || '...' AS stored FROM pg_authid WHERE rolname IN ('demo_scram','demo_md5') ORDER BY rolname" rolname | stored
------------+---------------
demo_md5 | md535075f1...
demo_scram | SCRAM-SHA-...
-- in full, the SCRAM verifier begins:
SCRAM-SHA-256$4096:eE7...An md5 prefix means the rest is md5(password || rolname). A
SCRAM-SHA-256$ prefix is followed by the iteration count — 4096 —
then a salt and two derived keys.
The default for new passwords is SCRAM on every supported version:
$ psql -U postgres -tAc 'SHOW password_encryption'scram-sha-256Why the difference matters operationally
An MD5 verifier is a usable credential. The MD5 authentication
exchange has the client send a value derived from the stored hash
rather than from the password, so an attacker who reads pg_authid can
authenticate without ever knowing the password. The hash is the
credential. It is also unsalted beyond the role name, so identical
passwords under the same role name produce identical hashes, and the
whole space is amenable to precomputation.
A SCRAM verifier is not. SCRAM is a challenge-response exchange in
which the client proves it knows the password without transmitting
anything the server could replay elsewhere, and the stored verifier is
salted with a per-role random value and iterated 4096 times. Reading
pg_authid yields something an attacker must attack offline rather
than something they can present.
That distinction changes the blast radius of a backup. A logical dump
taken with pg_dumpall includes role definitions and their verifiers.
With MD5, that file contains working credentials for every role. With
SCRAM, it contains material that must be cracked first.
The PostgreSQL 18 deprecation
PostgreSQL 18 warns when a password is stored with MD5, and states that support will be removed.
$ psql -U postgres -c "SET password_encryption='md5'; CREATE ROLE demo_md5 LOGIN PASSWORD '...'"WARNING: setting an MD5-encrypted password
DETAIL: MD5 password support is deprecated and will be removed in a future
release of PostgreSQL.
HINT: Refer to the PostgreSQL documentation for details about migrating to
another password type.The warning is controlled by md5_password_warnings, which defaults to
on. Turning it off is available and is the wrong response: the
warning is the estate’s inventory mechanism, and silencing it removes
the signal that tells you which roles still need migrating.
Migrating an estate to SCRAM
The migration is not a switch, because a stored MD5 verifier cannot be converted — the original password is not recoverable from it. Every affected role must have its password set again.
The sequence that avoids a lockout:
# 1. Inventory. Which roles still have an MD5 verifier?
psql -U postgres -c \
"SELECT rolname, rolcanlogin,
CASE WHEN rolpassword LIKE 'SCRAM-SHA-256%' THEN 'scram'
WHEN rolpassword LIKE 'md5%' THEN 'md5'
WHEN rolpassword IS NULL THEN 'none'
ELSE 'other' END AS verifier
FROM pg_authid WHERE rolcanlogin ORDER BY verifier, rolname"
# 2. Confirm every client library in use supports SCRAM.
# This is the step that actually blocks migrations.
# 3. Set the server default, so new passwords are written as SCRAM
psql -U postgres -c "ALTER SYSTEM SET password_encryption = 'scram-sha-256'"
psql -U postgres -c 'SELECT pg_reload_conf()'
# 4. Re-set each role's password. The value may be the same; what
# changes is the verifier that gets stored.
psql -U postgres -c "ALTER ROLE app_web PASSWORD 'the-existing-password'"
# 5. Only once every role is migrated, change pg_hba.conf from md5
# to scram-sha-256 and reload.
The ordering matters, and the reason is a compatibility asymmetry that is worth seeing rather than taking on trust. Both rule types were tested against both verifier formats on a live 18.6 cluster:
$ psql 'host=127.0.0.1 user=... dbname=postgres sslmode=disable' -tAc 'SELECT current_user'-- pg_hba rule: md5
demo_scram -> demo_scram (accepted)
demo_md5 -> demo_md5 (accepted)
-- pg_hba rule: scram-sha-256
demo_md5 -> FATAL: password authentication failed for user "demo_md5"
demo_scram -> demo_scram (accepted)pg_hba rule | SCRAM verifier | MD5 verifier |
|---|---|---|
md5 | accepted | accepted |
scram-sha-256 | accepted | rejected |
An md5 rule accepts both. A scram-sha-256 rule accepts only one.
That asymmetry is what makes the ordering safe: you can complete the
password migration entirely under the existing md5 rules, verifying
each role as you go, and change the rule only when nothing depends on
the old behaviour.
Step 5 before step 4 locks out every role still holding an MD5 verifier, all at once, at reload.
Note the failure message: password authentication failed. It is
indistinguishable from a wrong password, so an operator who makes this
mistake will reasonably start by investigating credentials rather than
verifier formats.
Production discipline
- Inventory verifier formats before assuming an estate is on
SCRAM. One query against
pg_authidgives the answer. - Re-set every password before changing
pg_hba.conf.md5accepts both formats;scram-sha-256accepts only one. - Leave
md5_password_warningson. It is the inventory mechanism, and silencing it removes the signal. - Verify client library support first. It is the step that actually blocks these migrations, and it fails only once the verifier changes.
- Treat
pg_dumpall --globals-onlyoutput as a credential store, and keep it separate from the data dump. - Remember an MD5 verifier is a working credential, so a leaked one requires rotation rather than assessment.
Cross-course references
- Secrets, PKI & Certificate Management — Part II (Secret lifecycle) covers rotation without downtime, which is the pattern the migration above follows, and Part XVIII (Incidents) covers responding to a leaked verifier.
- Linux for Production Sysadmins — Part XXVII (Auth) covers the equivalent hash-format migration in the host password stack.
- Git, CI/CD & GitOps — Part XXXV (Secrets in Git) covers the accidental commit of a globals dump, which is how these files usually escape.
Quiz
Knowledge check · 6 questions
Q1. An attacker obtains a copy of pg_authid containing MD5 verifiers. What is the consequence?
Q2. During a migration to SCRAM, an operator changes pg_hba.conf from md5 to scram-sha-256 and reloads before re-setting any passwords. What happens?
Q3. Which are true of the SCRAM exchange? Select all that apply.
Q4. Setting md5_password_warnings to off is a reasonable way to reduce log noise during an MD5 migration.
Q5. Give the correct order of steps for migrating an estate from md5 to scram-sha-256, and say why the ordering matters.
Q6. Assess the exposure and give the response.
A repository audit finds that a file named globals.sql was committed to an internal Git repository fourteen months ago and has been present in every clone since. It is the output of pg_dumpall --globals-only from the production cluster at that time. The cluster was running PostgreSQL 14 with password_encryption set to md5, and was upgraded to 18 nine months ago with no password changes. Roughly 40 engineers have cloned the repository.
Passing score: 75%. Answers are checked in this browser.