Si hoy todavía usas md5 en PostgreSQL/PgBouncer “porque siempre funcionó”, estás caminando hacia un problema anunciado: PostgreSQL ya deprecó MD5 y avisa que será removido en una versión mayor futura. PostgreSQL+1
La alternativa real (y la que vale la pena entender) es SCRAM-SHA-256.
1) Qué es SCRAM-SHA-256 en una frase (y por qué importa)
SCRAM-SHA-256 es un mecanismo de autenticación challenge-response que:
evita que la contraseña viaje “usable” por la red (reduce el riesgo de sniffing),
y permite que el servidor no almacene la contraseña, sino secretos derivados (más resistente ante fugas). PostgreSQL+2IETF Datatracker+2
PostgreSQL lo implementa como método scram-sha-256 (basado en RFC 7677). PostgreSQL
2) El diferencial 🔥: “SCRAM no es un hash como MD5”
Con md5 mucha gente piensa: “guardo el hash, listo”. El problema es que ese hash acaba siendo una especie de contraseña reutilizable en varios escenarios.
Con SCRAM, lo que se guarda es un paquete de secretos (derivados con salt + iteraciones) que sirven para verificar pruebas criptográficas, no para reconstruir una “contraseña lista para usar”.
De hecho, PgBouncer lo explica de forma muy directa:
“El secreto SCRAM almacenado no puede, por sí solo, usarse para derivar credenciales de login.” PgBouncer
Ese punto (que parece teórico) es el que te salva de varios ataques… y también es el que genera “bugs raros” si no lo entiendes al integrar PgBouncer.
3) Cómo funciona SCRAM (sin humo): el mini-handshake
En lugar de mandar password, SCRAM hace un ida-y-vuelta con:
nonce del cliente + nonce del servidor (evita replay),
salt + iteration count (endurece fuerza bruta),
una prueba del cliente (“client proof”) y una firma del servidor (verificación mutua). IETF Datatracker+2Crunchy Data+2
Idea mental:
“Te demuestro que sé el secreto sin mostrártelo.”
4) Qué guarda PostgreSQL realmente (y cómo lo verificas)
En PostgreSQL, la contraseña SCRAM queda almacenada (en pg_authid.rolpassword) con este formato:
SCRAM-SHA-256$<iterations>:<salt>$<StoredKey>:<ServerKey> PostgreSQL
Ejemplo de chequeo (solo para superuser/roles con acceso):
SELECT rolname,
rolpassword ~ '^SCRAM-SHA-256\$' AS es_scram
FROM pg_authid
WHERE rolcanlogin
ORDER BY 1;
5) Por qué PgBouncer + SCRAM es donde se gana (o se rompe) la seguridad
PgBouncer tiene su propia autenticación de clientes (auth_type) y su propio “directorio” de credenciales (auth_file).
5.1 auth_file con SCRAM (sí, se puede)
PgBouncer documenta el formato del archivo de auth así:
"username" "SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>"
y remarca que el archivo sirve para dos cosas:
autenticar clientes que entran a PgBouncer
autenticar conexiones salientes hacia PostgreSQL (si la DB lo requiere) PgBouncer
5.2 El “detalle mortal” (🔥) cuando PgBouncer también debe loguear a Postgres
PgBouncer advierte una limitación clave:
los secretos SCRAM NO siempre sirven para loguearse a Postgres a menos que se cumplan condiciones específicas (misma sal/iteraciones/secretos en ambos lados, etc.). PgBouncer
Traducción práctica:
Puedes tener SCRAM perfecto para cliente → PgBouncer,
pero fallar en PgBouncer → PostgreSQL si no alineaste cómo se guardó ese secreto.
6) Migración limpia de MD5 a SCRAM (sin cortar producción)
Paso A — Habilita que nuevas passwords se guarden como SCRAM
En PostgreSQL:
password_encryption = scram-sha-256
(Esto afecta cómo se guardan nuevas contraseñas cuando haces ALTER ROLE ... PASSWORD ....) PostgreSQL+1
Paso B — Rota passwords de roles que usan login (esto es lo que “mueve” a SCRAM)
Ejemplo:
ALTER ROLE odoo PASSWORD 'una_clave_larga_y_unica';
Paso C — Cambia pg_hba.conf a scram-sha-256
En la entrada correspondiente:
host all all 10.0.0.0/8 scram-sha-256
💡 Tip útil: PostgreSQL facilita transición porque si en pg_hba.conf pones md5 pero el usuario ya tiene password SCRAM, PostgreSQL puede elegir SCRAM automáticamente para ese usuario. PostgreSQL+1
Paso D — Ajusta PgBouncer
En pgbouncer.ini:
[pgbouncer] auth_type = scram-sha-256 auth_file = /etc/pgbouncer/userlist.txt scram_iterations = 4096
scram_iterations existe para aumentar el costo de fuerza bruta (más seguro, pero autentica más lento). PgBouncer
7) Seguridad real: SCRAM no reemplaza TLS
SCRAM mejora autenticación (y el storage del secreto), pero no cifra consultas ni resultados.
Si tu tráfico DB cruza redes que no controlas, usa TLS igualmente. (SCRAM + TLS es el combo serio; los RFC mismos lo plantean como deseable.) RFC Editor
8) Checklist rápido (para pegar al final del post)
PostgreSQL guarda passwords como SCRAM (password_encryption = scram-sha-256) PostgreSQL+1
pg_hba.conf usa scram-sha-256
Confirmé en pg_authid quién ya está en SCRAM PostgreSQL
PgBouncer tiene auth_type = scram-sha-256 y auth_file correcto PgBouncer+1
Entendí la limitación: secreto SCRAM ≠ credencial reutilizable PgBouncer
(Opcional) TLS habilitado si hay red no confiable RFC Editor
Plan para abandonar MD5 (ya está deprecado) PostgreSQL+1