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

Deja una respuesta