Hash con passlib

Intermedio
FastAPI
FastAPI
Actualizado: 01/07/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Instalación y configuración de passlib

Cuando desarrollamos aplicaciones web que manejan usuarios, uno de los aspectos más críticos es la gestión segura de contraseñas. Nunca debemos almacenar contraseñas en texto plano en nuestra base de datos, ya que esto representa un riesgo de seguridad enorme si alguien accede a nuestros datos.

La solución es utilizar funciones hash que transforman las contraseñas en cadenas irreversibles. Para esto, necesitamos una biblioteca especializada que implemente algoritmos de hash seguros y actualizados.

¿Qué es passlib?

Passlib es una biblioteca de Python diseñada específicamente para el manejo seguro de contraseñas. Proporciona una interfaz unificada para trabajar con múltiples algoritmos de hash y se encarga automáticamente de los aspectos técnicos de seguridad que necesitamos.

Esta biblioteca es especialmente útil porque:

  • Simplifica el proceso de hash y verificación de contraseñas
  • Maneja automáticamente la generación de valores únicos para cada contraseña
  • Utiliza algoritmos modernos y seguros por defecto
  • Se integra perfectamente con FastAPI y otras aplicaciones web

Instalación de passlib

Para instalar passlib en nuestro proyecto FastAPI, utilizamos pip con las dependencias necesarias para el algoritmo de hash que vamos a emplear:

pip install "passlib[bcrypt]"

El parámetro [bcrypt] instala las dependencias adicionales necesarias para utilizar el algoritmo bcrypt, que es uno de los más recomendados para el hash de contraseñas.

Si trabajas con un archivo requirements.txt, añade esta línea:

passlib[bcrypt]==1.7.4

Configuración básica

Una vez instalada la biblioteca, necesitamos configurar el contexto de hash en nuestro proyecto FastAPI. Crea un archivo llamado security.py donde centralizaremos toda la lógica de seguridad:

from passlib.context import CryptContext

# Configuración del contexto de hash
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

El objeto CryptContext es el núcleo de passlib. Los parámetros que utilizamos son:

  • schemes=["bcrypt"]: Especifica que utilizaremos el algoritmo bcrypt
  • deprecated="auto": Permite que passlib maneje automáticamente las actualizaciones de algoritmos

Estructura del proyecto

Para mantener nuestro código organizado, la estructura recomendada sería:

mi_proyecto/
├── main.py
├── security.py
├── models.py
└── requirements.txt

En el archivo security.py mantendremos todas las funciones relacionadas con la seguridad, mientras que main.py contendrá nuestras rutas de FastAPI.

Verificación de la instalación

Para confirmar que passlib está correctamente instalado y configurado, podemos crear un pequeño script de prueba:

from passlib.context import CryptContext

# Crear el contexto
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# Probar la configuración
test_password = "mi_contraseña_secreta"
hashed = pwd_context.hash(test_password)

print(f"Contraseña original: {test_password}")
print(f"Hash generado: {hashed}")
print(f"Verificación exitosa: {pwd_context.verify(test_password, hashed)}")

Si ejecutas este código y obtienes una salida similar a esta, la instalación es correcta:

Contraseña original: mi_contraseña_secreta
Hash generado: $2b$12$xyz...
Verificación exitosa: True

El hash generado será diferente cada vez que ejecutes el código, lo cual es exactamente el comportamiento que buscamos para mantener la seguridad.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Hash y verificación de contraseñas

Ahora que tenemos passlib configurado, vamos a implementar las funciones fundamentales para el manejo seguro de contraseñas: crear hashes y verificar que una contraseña coincide con su hash almacenado.

Creación de funciones de utilidad

En nuestro archivo security.py, añadiremos dos funciones esenciales que utilizaremos en toda nuestra aplicación:

from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str) -> str:
    """
    Genera un hash seguro de la contraseña proporcionada
    """
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) -> bool:
    """
    Verifica si una contraseña en texto plano coincide con su hash
    """
    return pwd_context.verify(plain_password, hashed_password)

La función hash_password() toma una contraseña en texto plano y devuelve su versión hasheada que podemos almacenar de forma segura. La función verify_password() compara una contraseña introducida por el usuario con el hash almacenado, devolviendo True si coinciden.

Integración con FastAPI

Vamos a crear un ejemplo práctico de cómo utilizar estas funciones en una aplicación FastAPI. Primero, definimos un modelo para el registro de usuarios:

from pydantic import BaseModel

class UserCreate(BaseModel):
    username: str
    password: str

class User(BaseModel):
    id: int
    username: str
    hashed_password: str

Ahora implementamos las rutas para registro y verificación de usuarios:

from fastapi import FastAPI, HTTPException
from security import hash_password, verify_password

app = FastAPI()

# Simulamos una base de datos en memoria
fake_users_db = {}
user_id_counter = 1

@app.post("/register")
async def register_user(user: UserCreate):
    global user_id_counter
    
    # Verificar si el usuario ya existe
    if user.username in fake_users_db:
        raise HTTPException(status_code=400, detail="El usuario ya existe")
    
    # Hashear la contraseña antes de almacenarla
    hashed_password = hash_password(user.password)
    
    # Crear el usuario en la "base de datos"
    fake_users_db[user.username] = {
        "id": user_id_counter,
        "username": user.username,
        "hashed_password": hashed_password
    }
    
    user_id_counter += 1
    
    return {"message": "Usuario registrado exitosamente"}

Verificación de credenciales

Para implementar un sistema de login básico, creamos una función que verifique las credenciales del usuario:

class UserLogin(BaseModel):
    username: str
    password: str

def authenticate_user(username: str, password: str):
    """
    Autentica un usuario verificando sus credenciales
    """
    user = fake_users_db.get(username)
    if not user:
        return False
    
    if not verify_password(password, user["hashed_password"]):
        return False
    
    return user

@app.post("/login")
async def login_user(user_login: UserLogin):
    user = authenticate_user(user_login.username, user_login.password)
    
    if not user:
        raise HTTPException(
            status_code=401, 
            detail="Credenciales incorrectas"
        )
    
    return {"message": f"Bienvenido, {user['username']}!"}

Ejemplo práctico completo

Aquí tienes un ejemplo completo que puedes probar para entender el flujo completo de registro y autenticación:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from security import hash_password, verify_password

app = FastAPI()

class UserCreate(BaseModel):
    username: str
    password: str

class UserLogin(BaseModel):
    username: str
    password: str

# Base de datos simulada
fake_users_db = {}

@app.post("/register")
async def register_user(user: UserCreate):
    if user.username in fake_users_db:
        raise HTTPException(status_code=400, detail="El usuario ya existe")
    
    # El hash se genera automáticamente con valores únicos
    hashed_password = hash_password(user.password)
    
    fake_users_db[user.username] = {
        "username": user.username,
        "hashed_password": hashed_password
    }
    
    return {"message": "Usuario registrado exitosamente"}

@app.post("/login")
async def login_user(user_login: UserLogin):
    stored_user = fake_users_db.get(user_login.username)
    
    if not stored_user:
        raise HTTPException(status_code=401, detail="Usuario no encontrado")
    
    # Verificamos la contraseña contra el hash almacenado
    if not verify_password(user_login.password, stored_user["hashed_password"]):
        raise HTTPException(status_code=401, detail="Contraseña incorrecta")
    
    return {"message": f"Login exitoso para {user_login.username}"}

Consideraciones importantes

Cada vez que ejecutes hash_password() con la misma contraseña, obtendrás un hash diferente. Esto es normal y deseable para la seguridad, ya que cada hash incluye valores únicos que previenen ataques de diccionario.

# Ejemplo de comportamiento normal
password = "mi_contraseña"
hash1 = hash_password(password)
hash2 = hash_password(password)

print(hash1)  # $2b$12$abc123...
print(hash2)  # $2b$12$def456...
print(hash1 == hash2)  # False - ¡Esto es correcto!

# Pero ambos se verifican correctamente
print(verify_password(password, hash1))  # True
print(verify_password(password, hash2))  # True

La función verify_password() es la única forma segura de comprobar si una contraseña es correcta. Nunca intentes comparar hashes directamente, ya que cada hash es único incluso para la misma contraseña.

Aprendizajes de esta lección

  • Comprender la importancia de no almacenar contraseñas en texto plano.
  • Instalar y configurar passlib con el algoritmo bcrypt.
  • Implementar funciones para generar y verificar hashes de contraseñas.
  • Integrar la gestión segura de contraseñas en una aplicación FastAPI.
  • Entender el comportamiento de los hashes únicos y la verificación segura de contraseñas.

Completa FastAPI y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración