Hash de contraseñas con bcrypt en Python

Publicado el: 11/07/2026
Tempo de leitura: 5 minutos
Criação de hashes seguros para senhas usando Python

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 bcrypt

Activa 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 False

Capturar 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_objetivo

Si 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Uso de dataclasses para simplificar classes em Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Dataclasses en Python: clases de datos más limpias

    Aprende dataclasses en Python: campos, valores predeterminados, field, frozen, order, __post_init__, herencia, asdict y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Problemas de travamento com threading em scripts Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Threading en Python: evita que tus scripts se bloqueen

    Aprende threading en Python: hilos, tareas de entrada y salida, join, locks, colas, ThreadPoolExecutor, errores y límites del GIL.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Dicas para otimizar scripts Python lentos
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Por qué tu script Python es lento y cómo mejorarlo

    Descubre por qué un script Python es lento y cómo mejorarlo con cProfile, timeit, estructuras correctas, generadores y mejores algoritmos.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Leitura de variáveis de ambiente em projetos Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Variables de entorno en Python: guía segura

    Aprende a usar variables de entorno en Python, archivos .env, validación, conversión de tipos, secretos, pruebas y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Introdução ao uso de type hints em Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Type hints en Python: escribe código más claro

    Aprende type hints en Python con funciones, colecciones, Optional, TypedDict, Callable, genéricos, Protocol y herramientas de análisis.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Medição de tempo de execução de código Python com timeit
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Timeit en Python: medir el rendimiento correctamente

    Aprende timeit en Python para comparar funciones, repetir benchmarks, separar setup, medir por operación y evitar errores de microoptimización.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026