Skip to main content

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

RuleWhat it judgesWhen it can fire
SEC.AUTH.ROLE_DEPRECATED_PASSWORD_HASHan account whose stored verifier is md5once a weak password exists
SEC.AUTH.HBA_MD5an authentication rule that negotiates md5once a rule in pg_hba.conf says so
This onewhat the next password will be stored asimmediately

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.