Hashing de Contraseñas en Producción
Hashea y verifica contraseñas de forma segura usando bcrypt, scrypt y Argon2 con mejores prácticas.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Visión General
Almacenar contraseñas de forma segura es una de las responsabilidades más críticas de cualquier aplicación. Los algoritmos modernos de hashing como bcrypt, scrypt y Argon2 están diseñados para ser lentos e intensivos en memoria, haciendo que los ataques de fuerza bruta sean computacionalmente inviables incluso si la base de datos es comprometida.
Cuándo Usar
Usa este recurso cuando:
- Implementas autenticación de usuarios desde cero
- Migras de hashing legacy (MD5, SHA-1) a algoritmos modernos
- Eliges parámetros para bcrypt, scrypt o Argon2
- Auditas un sistema de autenticación existente
Solución
bcrypt (Node.js)
const bcrypt = require('bcrypt');
async function hashPassword(password) {
const saltRounds = 12; // Ajusta según hardware (10-14 típico)
const hash = await bcrypt.hash(password, saltRounds);
return hash;
}
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
Argon2 (Python)
import argon2
ph = argon2.PasswordHasher(
time_cost=3, # Iteraciones
memory_cost=65536, # 64 MB en KiB
parallelism=4 # Hilos
)
def hash_password(password: str) -> str:
return ph.hash(password)
def verify_password(password: str, hash: str) -> bool:
try:
ph.verify(hash, password)
return True
except argon2.exceptions.VerifyMismatchError:
return False
scrypt (Go)
package main
import (
"golang.org/x/crypto/scrypt"
"crypto/rand"
"encoding/base64"
)
func hashPassword(password string) (string, error) {
salt := make([]byte, 16)
rand.Read(salt)
hash, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
if err != nil { return "", err }
return base64.StdEncoding.EncodeToString(salt) + "$" + base64.StdEncoding.EncodeToString(hash), nil
}
Explicación
| Algoritmo | Intensivo en Memoria | Configurable | Recomendado Para |
|---|---|---|---|
| bcrypt | No | Solo factor de costo | Uso general, amplio soporte de librerías |
| scrypt | Sí | Costo + memoria + paralelismo | Embebido, proyectos Go |
| Argon2 | Sí (ganador de PHC) | Tiempo + memoria + paralelismo | Nuevos proyectos, máxima seguridad |
Reglas críticas:
- Nunca inventes tu propia criptografía. Usa librerías bien validadas. Sigue mejores prácticas de seguridad.
- El salt debe ser único por contraseña y almacenado junto al hash.
- El pepper (secreto del lado del servidor) agrega defensa en profundidad pero no sustituye el hashing. Almacena peppers en un secret manager.
- Re-hashear en login si los parámetros de costo aumentan.
Variantes
| Lenguaje | Librería | Algoritmo | Notas |
|---|---|---|---|
| Node.js | bcrypt | bcrypt | Más popular; bindings nativos |
| Python | argon2-cffi | Argon2 | Ganador del Password Hashing Competition |
| Go | golang.org/x/crypto | scrypt, bcrypt, Argon2 | Extensiones de librería estándar |
| Java | spring-security-crypto | bcrypt, Argon2 | Abstracción de Spring |
| Rust | argon2 | Argon2 | Soporte zeroize para limpieza de memoria |
Mejores Prácticas
- Usa Argon2id para proyectos nuevos: Ganó el Password Hashing Competition (PHC)
- Apunta a 250ms de tiempo de verificación: Ajusta factores de costo a tu hardware
- Almacena salts con hashes: El salt no es secreto; prepéndelo al hash
- Agrega un pepper: Un secreto del lado del servidor agregado a la contraseña antes de hashear
- Re-hashea en login: Actualiza hashes legacy transparentemente cuando los usuarios inician sesión
Errores Comunes
- Usar SHA-256 o MD5 para contraseñas: Algoritmos rápidos son triviales de atacar con GPUs. Sigue mejores prácticas de seguridad para almacenamiento de credenciales.
- Codificar salts en duro: Cada contraseña necesita un salt aleatorio único
- Ignorar ataques de timing: Usa comparación en tiempo constante (incluido en librerías modernas)
- Olvidar actualizar factores de costo: El hardware mejora; reajusta anualmente
- Almacenar contraseñas en texto plano: Incluso “temporalmente” es un riesgo catastrófico. Consulta mejores prácticas de seguridad.
Preguntas Frecuentes
P: ¿Qué algoritmo debería elegir en 2025? R: Argon2id es la elección recomendada para sistemas nuevos. bcrypt es aceptable si las librerías de Argon2 no están disponibles.
P: ¿Cómo migro usuarios de MD5 a Argon2? R: Re-hashea en el próximo login: verifica con MD5, luego hashea con Argon2 y reemplaza. Marca la migración en la base de datos.
P: ¿Debería hashear del lado del cliente antes de enviar? R: No. El hashing del lado del cliente no ofrece beneficio de seguridad sobre HTTPS y elimina protección del lado del servidor.
Recursos Relacionados
API Security Checklist — Authentication to Encryption
A comprehensive security checklist for APIs: authentication, authorization, input validation, rate limiting, encryption, logging, and deployment hardening.
GuideSecurity Best Practices Guide
A comprehensive guide to application security: authentication, authorization, input validation, secrets management, and common vulnerability prevention.
RecipeWebSocket Authentication and Security Patterns
How to authenticate WebSocket connections, implement token validation, and handle authorization for real-time messaging in production
RecipeProtect Web Forms Against CSRF Attacks
How to prevent Cross-Site Request Forgery attacks using synchronizer tokens, SameSite cookies, and double-submit cookie patterns.
RecipeImplement OAuth 2.0 PKCE for Single-Page Applications
How to implement the OAuth 2.0 PKCE flow in single-page applications to securely authenticate users without exposing client secrets