El módulo secrets de Python genera valores aleatorios adecuados para contraseñas, tokens, enlaces de recuperación, claves temporales e identificadores que no deberían ser predecibles. Está incluido en la biblioteca estándar y utiliza la fuente de entropía proporcionada por el sistema operativo. Para seguridad, esta diferencia es esencial: el módulo random fue diseñado para simulaciones y juegos, no para proteger cuentas.
Si necesitas aleatoriedad común para ejemplos, sorteos o datos reproducibles, consulta la guía del módulo random en Python. Para credenciales o tokens, utiliza secrets.
Por qué random no sirve para seguridad
random implementa un generador seudoaleatorio determinista. Si un atacante conoce o deduce el estado interno, puede predecir valores posteriores. Esto no representa un problema al mezclar una lista de preguntas, pero sí al crear un token de inicio de sesión.
import random
# No usar para seguridad
token_inseguro = random.randint(100000, 999999)
print(token_inseguro)El módulo secrets obtiene aleatoriedad del sistema:
import secrets
token_seguro = secrets.randbelow(900000) + 100000
print(token_seguro)Aun así, un código de seis dígitos tiene solo un millón de combinaciones. Debe tener caducidad, límite de intentos y protección contra abuso.
Generar enteros seguros con randbelow()
import secrets
indice = secrets.randbelow(10)
print(indice) # valor entre 0 y 9randbelow(n) devuelve un entero entre cero y n - 1. Es útil para seleccionar posiciones o construir códigos dentro de un rango definido.
Elegir un elemento con choice()
import secrets
colores = ["rojo", "verde", "azul", "negro"]
elegido = secrets.choice(colores)
print(elegido)La función se parece a random.choice(), pero usa una fuente segura. Para generar contraseñas, define un alfabeto explícito.
Crear una contraseña aleatoria
import secrets
import string
alfabeto = string.ascii_letters + string.digits + "!@#$%&*"
longitud = 20
contrasena = "".join(secrets.choice(alfabeto) for _ in range(longitud))
print(contrasena)Una contraseña larga y aleatoria suele ser más resistente que una corta con reglas complejas. Sin embargo, una política puede exigir al menos un carácter de cada grupo. Puedes validar y repetir la generación:
def crear_contrasena(longitud: int = 20) -> str:
if longitud < 16:
raise ValueError("La longitud mínima debe ser 16")
alfabeto = string.ascii_letters + string.digits + "!@#$%&*"
while True:
valor = "".join(secrets.choice(alfabeto) for _ in range(longitud))
if (
any(c.islower() for c in valor)
and any(c.isupper() for c in valor)
and any(c.isdigit() for c in valor)
and any(c in "!@#$%&*" for c in valor)
):
return valorLa guía para crear un generador seguro de contraseñas en Python desarrolla este proyecto con opciones de terminal y pruebas.
Generar tokens binarios
import secrets
datos = secrets.token_bytes(32)
print(datos)token_bytes() devuelve bytes aleatorios. Resulta apropiado cuando una biblioteca criptográfica espera una secuencia binaria. No conviertas estos bytes manualmente a texto usando codificaciones ambiguas; utiliza las funciones hexadecimales o URL-safe.
Tokens hexadecimales
token = secrets.token_hex(32)
print(token)El argumento indica la cantidad de bytes aleatorios. La representación hexadecimal utiliza dos caracteres por byte, por lo que 32 bytes generan una cadena de 64 caracteres.
Tokens seguros para URLs
token = secrets.token_urlsafe(32)
enlace = f"https://example.com/restablecer?token={token}"
print(enlace)token_urlsafe() produce texto compatible con URLs. Es una opción práctica para confirmación de correo, recuperación de contraseña y enlaces temporales. El token debe almacenarse de forma segura, tener fecha de expiración y ser de un solo uso.
No guardar tokens sensibles en texto plano
Si un token permite ejecutar una acción crítica, considera almacenar solo un hash del token en la base de datos. Cuando el usuario presenta el valor original, calculas su hash y comparas.
import hashlib
import secrets
original = secrets.token_urlsafe(32)
resumen = hashlib.sha256(original.encode("utf-8")).hexdigest()
# Guardar resumen, enviar original al usuario
print(original)
print(resumen)Si la base de datos se filtra, el atacante no obtiene directamente el token válido. No confundas este patrón con el almacenamiento de contraseñas: para contraseñas se necesitan algoritmos lentos especializados como bcrypt, Argon2 o scrypt. Consulta la guía de bcrypt en Python.
Comparar valores con compare_digest()
import secrets
esperado = "abc123"
recibido = "abc123"
if secrets.compare_digest(esperado, recibido):
print("Coinciden")compare_digest() reduce filtraciones relacionadas con diferencias de tiempo en comparaciones de secretos. Debes comparar valores del mismo tipo y longitud razonable.
Crear un código temporal
import secrets
from datetime import datetime, timedelta, timezone
codigo = f"{secrets.randbelow(1_000_000):06d}"
expira = datetime.now(timezone.utc) + timedelta(minutes=10)
print(codigo, expira)La seguridad no depende únicamente de la generación. Limita intentos, registra eventos, invalida el código después de usarlo y evita revelar si una cuenta existe.
Gestionar secretos de aplicación
No uses secrets para generar una clave nueva cada vez que inicia una aplicación si necesitas descifrar datos antiguos o mantener sesiones. Genera la clave una vez y almacénala fuera del código, por ejemplo en variables de entorno o un gestor de secretos.
La guía de variables de entorno en Python explica cómo separar configuración y código. Para producción, un servicio especializado ofrece rotación, auditoría y controles de acceso superiores a un archivo .env.
Generar identificadores
Los tokens aleatorios pueden servir como identificadores difíciles de adivinar:
identificador = secrets.token_urlsafe(18)
print(identificador)No obstante, para claves internas también existen UUID. La decisión depende de longitud, formato, distribución y requisitos de seguridad. No utilices identificadores secuenciales en URLs sensibles sin autorización adicional.
Pruebas y reproducibilidad
La seguridad de secrets implica que no puedes establecer una semilla para repetir exactamente una secuencia. En pruebas, separa la generación mediante una función inyectable:
def crear_codigo(generador=secrets.randbelow):
return f"{generador(1_000_000):06d}"Durante el test puedes pasar una función controlada. No sustituyas secrets por random en producción solo para facilitar pruebas.
Aplicación en un gestor de contraseñas
Un gestor puede usar secrets para generar credenciales nuevas, pero la protección del archivo requiere cifrado autenticado, una clave derivada correctamente y un diseño seguro. La guía para crear un gestor de contraseñas en Python muestra un ejemplo educativo y sus limitaciones.
Errores frecuentes
Los errores habituales son generar tokens demasiado cortos, usar códigos sin límite de intentos, guardar secretos en logs, incluir tokens en repositorios, reutilizar valores, comparar con == en contextos sensibles y asumir que un valor aleatorio sustituye la autorización.
También es peligroso enviar tokens en URLs que quedan registrados en historiales, analítica o encabezados Referer. Evalúa el canal, la duración y el alcance de cada secreto.
Fuentes confiables
La documentación oficial del módulo secrets explica cada función y la elección del número de bytes. Las recomendaciones de OWASP para recuperación de contraseñas cubren tokens, expiración, uso único, límites y respuestas seguras.
Conclusión
Utiliza secrets siempre que la imprevisibilidad proteja una cuenta, sesión o acción. randbelow(), choice(), token_hex() y token_urlsafe() cubren la mayoría de necesidades. La generación es solo una parte: almacenamiento, caducidad, comparación, límites de intentos y autorización completan el diseño seguro.






