SEC.CFG.PASSWORD_ENCRYPTION — New passwords are stored with a deprecated hash
- Category: security
- Severity: high
- Level: 0
- Confidence: deterministic
- Downtime class: none — the finding is about a server parameter, not about a statement
- Stability: stable
- Suites: audit
- Applies to: PostgreSQL 18
Three md5 findings, three different times
| Rule | What it judges | When it can fire |
|---|---|---|
| SEC.AUTH.ROLE_DEPRECATED_PASSWORD_HASH | an account whose stored verifier is md5 | once a weak password exists |
| SEC.AUTH.HBA_MD5 | an authentication rule that negotiates md5 | once a rule in pg_hba.conf says so |
| This one | what the next password will be stored as | immediately |
Only the last is preventable before the fact. A server with password_encryption = md5 and no md5
passwords yet passes both of the others — and produces a new md5 verifier for every
ALTER ROLE … PASSWORD and every \password from now on. Nothing fails, nothing warns at the
connection, and no migration mentions it.
Why md5 in particular
PostgreSQL's md5 verifier is salted with the role name and nothing else.
Two servers carrying a role called app therefore produce the same digest for the same
password. A digest captured anywhere is crackable offline and replayable as the password itself
against every other server that role exists on.
scram-sha-256 has a per-password random salt and an iteration count, and it never puts anything
replayable on the wire.
What it looks like
# postgresql.conf: every password set from now on is stored as an md5 verifier.
password_encryption = md5
What to do about it
# The default since PostgreSQL 14. A configuration reload picks this up; no restart.
password_encryption = scram-sha-256
SELECT pg_reload_conf(); is enough, and there is no downtime to plan.
The two things this does not say
It does not say your stored passwords are fine. Changing the parameter leaves every existing verifier exactly as it was; each account has to have its password set again. What you already have is reported per account, by SEC.AUTH.ROLE_DEPRECATED_PASSWORD_HASH.
It is a default, not an enforcement. The parameter has context user — any session may set it
for itself, and one that does writes an md5 verifier however the server is configured. Fixing it
stops the server producing them by default; it does not make them impossible.