Las contraseñas nunca deben guardarse como texto plano. Si una base de datos se filtra, cada credencial quedaría expuesta de inmediato. La solución correcta es aplicar una función de derivación diseñada para contraseñas, con salt y un coste configurable. bcrypt es una opción ampliamente utilizada porque hace que cada comprobación sea deliberadamente costosa y dificulta los ataques masivos.
Esta guía explica cómo usar bcrypt en Python, qué información contiene el hash, cómo verificar credenciales y qué errores debes evitar. Para generar contraseñas aleatorias en herramientas administrativas, revisa el proyecto de generador seguro de contraseñas.
Instalar bcrypt en un entorno virtual
python -m venv .venv
python -m pip install bcryptActiva el entorno correspondiente antes de instalar. La guía de venv en Python explica cómo aislar dependencias y evitar conflictos entre proyectos.
Crear el primer hash
La biblioteca trabaja con bytes, por lo que una cadena debe codificarse:
import bcrypt
contrasena = "MiClaveSegura!2026"
datos = contrasena.encode("utf-8")
salt = bcrypt.gensalt()
hash_guardado = bcrypt.hashpw(datos, salt)
print(hash_guardado.decode("utf-8"))gensalt() crea un salt aleatorio y añade la información de versión y coste. El resultado de hashpw() contiene todo lo necesario para verificar más tarde, excepto la contraseña original.
Por qué no debes guardar el salt por separado
El string generado incluye el identificador del algoritmo, el coste, el salt y el resultado. Puedes almacenarlo en una columna de texto o bytes. No es necesario crear otra columna para el salt cuando utilizas la representación completa de bcrypt.
Verificar una contraseña
import bcrypt
entrada = "MiClaveSegura!2026"
hash_guardado = b"$2b$12$..."
if bcrypt.checkpw(entrada.encode("utf-8"), hash_guardado):
print("Acceso permitido")
else:
print("Credenciales inválidas")checkpw() extrae el coste y el salt del hash guardado, calcula el resultado para la entrada y realiza la comparación. No vuelvas a generar un salt para verificar, porque produciría un valor diferente.
Función de registro y autenticación
import bcrypt
def crear_hash(contrasena: str) -> str:
datos = contrasena.encode("utf-8")
return bcrypt.hashpw(datos, bcrypt.gensalt()).decode("utf-8")
def verificar(contrasena: str, hash_texto: str) -> bool:
try:
return bcrypt.checkpw(
contrasena.encode("utf-8"),
hash_texto.encode("utf-8"),
)
except ValueError:
return FalseCapturar ValueError evita que un hash malformado detenga la aplicación. Registra el problema internamente, pero muestra al usuario un mensaje genérico para no revelar detalles del sistema.
Elegir el factor de coste
El parámetro rounds controla el trabajo:
salt = bcrypt.gensalt(rounds=12)
hash_guardado = bcrypt.hashpw(datos, salt)Un coste mayor aumenta el tiempo de hash y verificación. Debe ser suficientemente lento para dificultar ataques, pero aceptable para usuarios legítimos y para la capacidad del servidor. Mide en el hardware real y revisa el valor con el tiempo.
Actualizar hashes antiguos
Cuando elevas el coste, no necesitas restablecer todas las contraseñas. Después de una autenticación correcta puedes identificar el coste guardado y crear un hash nuevo.
def necesita_rehash(hash_texto: str, coste_objetivo: int = 12) -> bool:
partes = hash_texto.split("$")
return len(partes) > 2 and int(partes[2]) < coste_objetivoSi la comprobación devuelve verdadero, genera un nuevo hash usando la contraseña que el usuario acaba de proporcionar y actualiza la base de datos dentro de una transacción.
El límite de longitud de bcrypt
Bcrypt tradicionalmente procesa solo los primeros 72 bytes de la contraseña. Una cadena con caracteres Unicode puede alcanzar ese límite con menos de 72 caracteres. La versión de la biblioteca y su configuración pueden reaccionar de distintas formas. Define una política compatible, valida el tamaño en bytes y no trunques silenciosamente.
datos = contrasena.encode("utf-8")
if len(datos) > 72:
raise ValueError("La contraseña supera el límite admitido")Para sistemas nuevos, evalúa las recomendaciones actuales de seguridad y las alternativas modernas cuando tus requisitos incluyan contraseñas muy largas.
No utilizar SHA-256 directamente
SHA-256 es rápido. Esa velocidad es útil para integridad de archivos, pero peligrosa para contraseñas porque un atacante puede probar enormes cantidades por segundo. Añadir un salt manual a SHA-256 no introduce un coste adaptable. Utiliza bcrypt, Argon2id, scrypt o PBKDF2 según el contexto y las políticas de tu plataforma.
Leer contraseñas sin mostrarlas
En una aplicación de terminal utiliza getpass:
from getpass import getpass
contrasena = getpass("Contraseña: ")La guía para leer contraseñas de forma segura en la terminal explica confirmación y validación. Recuerda que ocultar la entrada no protege contra malware o registros inseguros.
Almacenamiento y base de datos
Guarda el hash completo, nunca la contraseña. Utiliza consultas parametrizadas, controles de acceso, cifrado del almacenamiento cuando corresponda y copias de seguridad protegidas. Un hash no convierte una base de datos pública en segura; reduce el daño cuando las credenciales se filtran.
Comparaciones y mensajes de error
No informes si falló el usuario o la contraseña. Devuelve un mensaje como “Credenciales inválidas” para ambos casos. También considera una respuesta temporalmente uniforme, límites de intentos y autenticación multifactor. Bcrypt protege el almacenamiento, no todo el proceso de autenticación.
Construir un gestor educativo
El proyecto de gestor de contraseñas en Python ayuda a practicar cifrado y almacenamiento. No confundas cifrado con hash: un gestor necesita recuperar secretos y por eso utiliza cifrado; un servidor de autenticación no necesita recuperar la contraseña y debe usar un hash irreversible.
Proteger secretos de configuración
Las claves de base de datos, peppers y tokens no deben estar en el repositorio. Consulta cómo leer variables de entorno de forma segura. Si tu arquitectura utiliza un pepper, guárdalo fuera de la base de datos y diseña un procedimiento de rotación; perderlo puede impedir todas las verificaciones.
Pruebas esenciales
def test_hash_cambia():
primero = crear_hash("misma-clave")
segundo = crear_hash("misma-clave")
assert primero != segundo
def test_verificacion():
hash_texto = crear_hash("correcta")
assert verificar("correcta", hash_texto)
assert not verificar("incorrecta", hash_texto)Dos hashes de la misma contraseña deben ser diferentes debido al salt, pero ambos deben verificarse correctamente.
Fuentes y recomendaciones
La página oficial de bcrypt en PyPI documenta instalación, APIs y cambios de versión. La guía de almacenamiento de contraseñas de OWASP compara algoritmos, costes y prácticas de migración.
Errores frecuentes
No reutilices un salt fijo, no generes un salt nuevo durante la verificación, no registres contraseñas, no envíes credenciales sin TLS, no utilices algoritmos rápidos y no establezcas un coste tan alto que permita ataques de denegación de servicio. Revisa además el límite de bytes y prueba caracteres Unicode.
Conclusión
Bcrypt proporciona una base sólida para almacenar contraseñas cuando se configura y utiliza correctamente. Genera un salt único, guarda el hash completo, verifica con checkpw(), mide el coste y prepara una estrategia de actualización. Combínalo con transporte cifrado, consultas seguras, límites de intentos y autenticación multifactor. La protección de credenciales es un sistema completo; el hash es una pieza crítica, pero no la única.






