10 Formas de Generar Contraseñas en Python (Solo Biblioteca Estándar)

Python incluye todo lo necesario para generar contraseñas y tokens seguros directamente en su biblioteca estándar. Sin pip install, sin dependencias externas. En este artículo recorremos 10 patrones prácticos de generación de contraseñas, desde cadenas aleatorias simples hasta generación por lotes sin duplicados. Todos los fragmentos funcionan con Python 3.6+.

Una regla antes de empezar: para cualquier cosa relacionada con seguridad, usa secrets en lugar de random. El módulo random está diseñado para simulaciones, no para generar credenciales. Se ven casi idénticos en el código, que es exactamente la razón por la que el error es tan común.

1. Contraseña Aleatoria Básica

El punto de partida más simple. Elige caracteres al azar de un conjunto mixto de minúsculas, mayúsculas, dígitos y algunos símbolos.

basic_password.py
import secrets

def generate_password(length=16):
    chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%"
    return "".join(secrets.choice(chars) for _ in range(length))

if __name__ == "__main__":
    for i in range(5):
        print(f"Password {i+1}: {generate_password()}")

Funciona bien para muchos casos, pero no hay garantía de que aparezca cada tipo de carácter. Si un servicio exige al menos un dígito o símbolo, salta al patrón 5.

2. Excluyendo Caracteres Confusos

Caracteres como 0 vs O, o 1 vs l son una fuente recurrente de problemas cuando las contraseñas se dictan por teléfono o se imprimen en papel.

no_confusing.py
import secrets
import string

def generate_password(length=16, excluded="0OIl1"):
    all_chars = string.ascii_letters + string.digits + "!@#$%"
    chars = "".join(c for c in all_chars if c not in excluded)
    if not chars:
        raise ValueError("No quedan caracteres utilizables.")
    return "".join(secrets.choice(chars) for _ in range(length))

if __name__ == "__main__":
    for i in range(5):
        print(f"Password {i+1}: {generate_password()}")

Eliminar caracteres reduce el pool, lo que disminuye ligeramente la entropía por carácter. En la práctica, añadir uno o dos caracteres extra a la longitud compensa con creces. La verdadera ganancia es menos tickets de soporte que empiezan con «¿eso es un cero o una O?«

3. Cadenas Aleatorias con Prefijo

A veces necesitas un prefijo reconocible—USER_, APIKEY_—seguido de una cola aleatoria.

prefixed.py
import secrets
import string

def generate_with_prefix(prefix="USER_", random_length=12):
    chars = string.ascii_letters + string.digits
    tail = "".join(secrets.choice(chars) for _ in range(random_length))
    return prefix + tail

if __name__ == "__main__":
    for i in range(5):
        print(f"Key {i+1}: {generate_with_prefix()}")

El prefijo añade cero entropía. Un prefijo de 6 caracteres más 12 aleatorios es exactamente tan fuerte como 12 caracteres aleatorios. Los prefijos son para humanos leyendo logs, no para confundir atacantes.

4. Generación por Lotes con Prefijo

La misma idea, pero genera un lote de una vez. Útil al incorporar un grupo de usuarios o provisionar claves API.

bulk_prefixed.py
import secrets
import string

def generate_batch(prefix="APIKEY_", random_length=16, count=5):
    chars = string.ascii_letters + string.digits
    return [
        prefix + "".join(secrets.choice(chars) for _ in range(random_length))
        for _ in range(count)
    ]

if __name__ == "__main__":
    for i, key in enumerate(generate_batch(), 1):
        print(f"Key {i}: {key}")

Sin verificación de duplicados aquí. Si generas millones de filas, el patrón 10 es más seguro. Y recuerda: en cuanto generas un lote, el mayor riesgo pasa de «¿es suficientemente aleatorio?» a «¿dónde termina esta lista?»

5. Mezcla de Caracteres Garantizada

Muchos servicios exigen: al menos una mayúscula, un dígito, un símbolo. Este patrón reserva un espacio por tipo requerido, llena el resto al azar y mezcla.

strong_password.py
import secrets
import string
import random

def generate_strong_password(length=16):
    if length < 4:
        raise ValueError("length debe ser >= 4")
    lower = string.ascii_lowercase
    upper = string.ascii_uppercase
    digits = string.digits
    symbols = "!@#$%^&*"
    parts = [
        secrets.choice(lower),
        secrets.choice(upper),
        secrets.choice(digits),
        secrets.choice(symbols),
    ]
    all_chars = lower + upper + digits + symbols
    parts += [secrets.choice(all_chars) for _ in range(length - 4)]
    random.shuffle(parts)
    return "".join(parts)

if __name__ == "__main__":
    for i in range(5):
        print(f"Password {i+1}: {generate_strong_password()}")

El random.shuffle() solo determina la posición de caracteres ya aleatorios, así que el impacto práctico es insignificante. Un error más sutil: fijar posiciones (primera letra siempre mayúscula, segunda siempre dígito). Eso crea un patrón, y los patrones son lo opuesto a lo que buscamos.

6. Contraseñas Legibles

Sin símbolos ni caracteres confusos. Ideal para contraseñas temporales que se entregan en papel o se dictan por teléfono.

readable.py
import secrets
import string

def generate_readable(length=16):
    excluded = "0OIl1"
    chars = "".join(
        c for c in string.ascii_letters + string.digits
        if c not in excluded
    )
    return "".join(secrets.choice(chars) for _ in range(length))

if __name__ == "__main__":
    for i in range(5):
        print(f"Password {i+1}: {generate_readable()}")

La seguridad no es solo entropía teórica. Una contraseña perfecta de 128 bits que acaba en un post-it es menos segura que una contraseña de 80 bits que la gente realmente usa bien.

7. Frases de Contraseña

Concatena palabras aleatorias con un separador. El resultado es largo (bueno para entropía) y memorable (bueno para humanos).

passphrase.py
import secrets

WORDS = [
    "river", "cloud", "apple", "stone", "forest",
    "ocean", "light", "shadow", "iron", "spark",
    "maple", "crane", "drift", "ember", "frost",
    "bloom", "cedar", "ridge", "pearl", "storm",
]

def generate_passphrase(word_count=4):
    return "-".join(secrets.choice(WORDS) for _ in range(word_count))

if __name__ == "__main__":
    for i in range(5):
        print(f"Passphrase {i+1}: {generate_passphrase()}")

La lista de ejemplo es deliberadamente corta. En producción, usa una lista mucho más grande. Y el pecado capital de las frases de contraseña: elegir tus propias palabras. «correct-horse-battery-staple» dejó de ser buena contraseña en el momento en que se convirtió en el ejemplo más citado del mundo.

8. Tokens Seguros para URL

Para tokens que puedes incrustar en una URL sin preocuparte por la codificación.

url_token.py
import secrets
import base64

def generate_token(byte_length=24):
    raw = secrets.token_bytes(byte_length)
    return base64.urlsafe_b64encode(raw).decode().rstrip("=")

if __name__ == "__main__":
    for i in range(5):
        print(f"Token {i+1}: {generate_token()}")

«Seguro para URL» significa que los caracteres funcionan bien en URLs. No significa que el token sea seguro si se expone. Los tokens en URLs pueden filtrarse por historial del navegador, logs del servidor y cabeceras Referer. Mantenlos con vida corta cuando sea posible.

9. Generación por Lotes Personalizada

Especifica la longitud y la cantidad, obtén una lista. Directo al grano.

batch.py
import secrets
import string

def generate_batch(length=16, count=5):
    chars = string.ascii_letters + string.digits + "!@#$%"
    return [
        "".join(secrets.choice(chars) for _ in range(length))
        for _ in range(count)
    ]

if __name__ == "__main__":
    for i, pw in enumerate(generate_batch(length=20, count=5), 1):
        print(f"Password {i}: {pw}")

Para cantidades grandes, escribe directamente a un archivo en lugar de imprimir en terminal. Los buffers de scroll del terminal tienen la costumbre de quedarse por ahí.

10. Lote Sin Duplicados

Cuando necesitas garantizar que no haya repeticiones—por ejemplo, emitir códigos únicos para una lista de usuarios.

unique_batch.py
import secrets
import string

def generate_unique(length=16, count=10):
    chars = string.ascii_letters + string.digits + "!@#$%"
    result = set()
    while len(result) < count:
        result.add("".join(secrets.choice(chars) for _ in range(length)))
    return list(result)

if __name__ == "__main__":
    for i, pw in enumerate(generate_unique(length=16, count=5), 1):
        print(f"Password {i}: {pw}")

Con 16 caracteres alfanuméricos+símbolos, las colisiones son prácticamente imposibles. Pero si alguien pasa length=4, count=100000, el bucle empezará a sufrir. Una verificación rápida de la proporción nunca está de más.

Conclusión

Los 10 patrones usan solo la biblioteca estándar de Python: secrets, string, random y base64. Sin instalaciones, sin dependencias.

Para contraseñas de inicio de sesión, el patrón 5 suele ser el punto óptimo. Para tokens de API, el patrón 8 es limpio y práctico. Para contraseñas maestras que se escriben a diario, el patrón 7 gana en usabilidad.

Y recuerda: generar una contraseña fuerte es la parte fácil. Almacenarla de forma segura, transmitirla correctamente y rotarla a tiempo es donde empieza el verdadero trabajo.

Comments

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *