Criptografía

Cómo generar contraseñas seguras con Python (y entender su nivel de seguridad)

Vivimos en un mundo cada vez más digital, donde gestionamos decenas (o incluso cientos) de cuentas en línea. En este contexto, utilizar contraseñas seguras no es una opción, sino una necesidad.

Pero, ¿qué significa realmente que una contraseña sea “segura”? ¿Y cómo podemos generar contraseñas robustas sin depender de servicios externos?

En esta entrada te mostraremos cómo generar contraseñas seguras utilizando Python, cómo evaluar su nivel de seguridad a través del concepto de entropía, y cómo generar frases seguras y fáciles de recordar (passphrases).

¿Qué hace que una contraseña sea segura?

Una contraseña segura debe cumplir, al menos, dos condiciones fundamentales:

  • Alta entropía: es decir, debe tener muchas combinaciones posibles, lo que dificulta su descubrimiento por fuerza bruta. Esto depende principalmente de la longitud y del conjunto de caracteres utilizados.
  • Impredecibilidad: aunque una contraseña sea larga, si sigue patrones simples (como "123456789012" o "aaaaaaaaaaaa"), será fácil de adivinar o descifrar mediante ataques dirigidos.

Entropía: ¿qué es y por qué importa?

La entropía es una medida de impredecibilidad o aleatoriedad, expresada en bits. Cuanto mayor es la entropía de una contraseña, más difícil es para un atacante probar todas las combinaciones posibles.

En teoría, la entropía de una contraseña generada aleatoriamente se calcula como: H = L \times \log_2(A), donde L es la longitud de la contraseña (en caracteres) y A es el tamaño del alfabeto utilizado.

El “tamaño del alfabeto” representa la cantidad de caracteres únicos que podrían aparecer en cada posición de la contraseña. Por ejemplo:

  • Letras minúsculas: 26
  • Letras mayúsculas: 26
  • Dígitos (0–9): 10
  • Símbolos comunes: 32 (aproximadamente)
  • Total posible: 94 caracteres

Así, una contraseña de 12 caracteres generada aleatoriamente usando el alfabeto completo tiene una entropía teórica de: 12 \times \log_2(94) \approx 78\,7 \text{ bits}

Importante: esta fórmula supone que cada carácter se elige de forma completamente aleatoria e independiente. Si introduces patrones (como fechas, repeticiones o secuencias), la entropía efectiva será mucho menor.

Ejemplos prácticos

Vamos a comparar dos contraseñas de 12 caracteres:

  1. "123456789012"
  • Longitud: 12
  • Alfabeto: 10 dígitos
  • Entropía teórica: 12 \times \log_2(10) \approx 39\,8 \text{ bits}
  • ¿Segura?: Muy predecible. Fácil de romper con ataques automatizados.
  1. "xR4!m8Zq@Tn#"
  • Longitud: 12
  • Alfabeto: 94 caracteres
  • Entropía teórica: 12 \times \log_2(94) \approx 78\,8 \text{ bits}
  • ¿Segura?: Alta entropía y apariencia aleatoria. Mucho más difícil de descifrar.

En resumen: la entropía solo refleja la seguridad si hay verdadera aleatoriedad. Una contraseña larga y predecible puede ser débil; una más corta pero bien generada puede ser muy fuerte.

¿Qué nivel de entropía se considera seguro?

No existe un estándar único, pero en general se recomienda:

Tipo de cuentaEntropía recomendadaLongitud mínima (alfabeto de 94)
Cuentas críticas≥ 80 bits14 caracteres o más
Cuentas moderadas40–60 bits8–10 caracteres

Tip: Si solo usas letras y números (62 caracteres), necesitarás más longitud para alcanzar la misma entropía. Por eso es recomendable incluir símbolos en las contraseñas.

Generar contraseñas aleatorias con secrets

Python ofrece el módulo secrets, diseñado específicamente para generar datos criptográficamente seguros. Es preferible al módulo random, ya que este último no es adecuado para aplicaciones de seguridad: secrets utiliza fuentes aleatorias de alta entropía como /dev/urandom en sistemas Unix.

Ejemplo 1: Contraseña alfanumérica segura

La forma más sencilla de generar una contraseña segura en Python es utilizando secrets.choice() para seleccionar caracteres aleatorios de un conjunto (alfabeto). Este conjunto puede incluir letras mayúsculas, minúsculas y dígitos. La siguiente función hace exactamente eso:

import secrets
import string
from math import log2

def generar_password(longitud: int = 12) -> str:
    """
    Genera una contraseña alfanumérica segura utilizando letras y dígitos.

    Args:
        longitud (int): Número de caracteres de la contraseña (por defecto 12).

    Returns:
        str: Contraseña generada aleatoriamente.
    """
    alfabeto = string.ascii_letters + string.digits
    return ''.join(secrets.choice(alfabeto) for _ in range(longitud))

# Ejemplo de uso
password = generar_password()
print(f"Contraseña generada: {password}")

# Cálculo de entropía teórica
entropia = 12 * log2(62)
print(f"Entropía aproximada: {entropia:.2f} bits")
Contraseña generada: 5SZaCTlBv9aR
Entropía aproximada: 71.45 bits

La entropía estimada para una contraseña de 12 caracteres alfanuméricos es de aproximadamente 71,5 bits, lo cual es una opción bastante sólida para uso general.

Ejemplo 2: Contraseña con símbolos especiales

Al incluir símbolos como !@#$%^&*(), aumentamos el tamaño del alfabeto, y por lo tanto la entropía. Esto hace que las contraseñas sean más resistentes a ataques de fuerza bruta.

La siguiente función permite especificar si se quieren incluir símbolos especiales o no:

def generar_password_segura(longitud: int = 16, incluir_simbolos: bool = True) -> str:
    """
    Genera una contraseña segura con la posibilidad de incluir símbolos especiales.

    Args:
        longitud (int): Número de caracteres de la contraseña (por defecto 16).
        incluir_simbolos (bool): Si se incluyen símbolos especiales en la contraseña.

    Returns:
        str: Contraseña generada aleatoriamente.
    """
    alfabeto = string.ascii_letters + string.digits 
    if incluir_simbolos:
        alfabeto += string.punctuation

    return ''.join(secrets.choice(alfabeto) for _ in range(longitud))

# Ejemplos de uso
print("Con símbolos: ", generar_password_segura(incluir_simbolos=True))
print("Sin símbolos: ", generar_password_segura(incluir_simbolos=False))

# Cálculo de entropía con símbolos
entropia = 16 * log2(len(string.ascii_letters + string.digits + string.punctuation))
print(f"Entropía aproximada: {entropia:.2f} bits")
Con símbolos:  Ieq#V^jD8Pp@BDx)
Sin símbolos: WDKIr3yFzVL7GazV
Entropía aproximada: 104.87 bits

Ahora, la entropía estimada de una contraseña de 16 caracteres utilizando todo el set ASCII (letras, dígitos y símbolos) puede alcanzar alrededor de 104 bits, lo cual se considera extremadamente seguro.

Generar frases seguras (passphrases)

Las passphrases son secuencias de palabras aleatorias. Son altamente seguras y, a diferencia de las contraseñas tradicionales, suelen ser más fáciles de recordar. Este concepto se popularizó por el famoso cómic de xkcd.

Ejemplo: "correcto-caballo-batalla-grapa"

Código para generar una passphrase segura y fácil de recordar

Usar una passphrase (una frase compuesta por varias palabras aleatorias) puede ser más fácil de memorizar que una contraseña compleja, y puede ofrecer una entropía comparable o superior si se eligen adecuadamente las palabras.

def cargar_diccionario(path: str = "/usr/share/dict/words") -> list[str]:
    """
    Carga un diccionario de palabras desde un archivo de texto plano.

    Parámetros:
    - path: Ruta al archivo que contiene el diccionario, una palabra por línea.

    Retorna:
    - Una lista de palabras en minúsculas y sin caracteres no alfabéticos.
    """
    with open(path, encoding='utf-8') as f:
        return [
            palabra.strip().lower()
            for palabra in f
            if palabra.strip().isalpha()
        ]

def generar_passphrase(diccionario: list[str], palabras: int = 4) -> str:
    """
    Genera una passphrase aleatoria seleccionando palabras del diccionario.

    Parámetros:
    - diccionario: Lista de palabras válidas para la passphrase.
    - palabras: Número de palabras que compondrán la passphrase (por defecto 4).

    Retorna:
    - Una cadena con las palabras seleccionadas separadas por espacios.
    """
    if len(diccionario) == 0:
        raise ValueError("El diccionario no puede estar vacío")

    return ' '.join(secrets.choice(diccionario) for _ in range(palabras))


# Cargamos el diccionario
diccionario = cargar_diccionario()

# Generamos una passphrase de 4 palabras
frase = generar_passphrase(diccionario)
print(f"Passphrase generada: {frase}")
Passphrase generada: flacked phaeacian wanghee evodia

En este código se ha cargado el diccionario por defecto de los sistemas UNIX como macOS o Linux.

Cálculo teórico de entropía de una passphrase

La entropía mide cuán impredecible es una contraseña. Para una passphrase generada al azar esta será:

  • Diccionario con 10,000 palabras, 4 palabras: H = 4 \times \log_2(10.000) \approx 53\,2 \text{ bits}
  • Diccionario con 50,000 palabras, 5 palabras: H = 5 \times \log_2(50.000) \approx 77\,5 \text{ bits}

Recomendaciones finales

A la hora de generar contraseñas seguras en Python es importante tener estos puntos en consideración:

  • Usa secrets siempre que necesites valores aleatorios seguros (tokens, claves, contraseñas…).
  • Las passphrases largas bien generadas pueden ser más seguras y más fáciles de recordar que las contraseñas con símbolos aleatorios.
  • No reutilices contraseñas, y considera el uso de gestores como Bitwarden, 1Password o KeePass.

Conclusiones

El módulo secrets de Python es la herramienta recomendada cuando se trata de generar contraseñas seguras, ya que ha sido diseñado específicamente para propósitos criptográficos y ofrece una aleatoriedad adecuada para resistir ataques.

Aunque las contraseñas tradicionales pueden ser robustas si se construyen con suficientes caracteres y variedad (mezclando letras mayúsculas, minúsculas, dígitos y símbolos), alcanzar un buen nivel de seguridad suele implicar una complejidad que puede dificultar su memorización. Por ejemplo, una contraseña aleatoria de 16 caracteres utilizando un conjunto de 94 símbolos distintos puede superar los 100 bits de entropía, lo cual es excelente desde el punto de vista criptográfico.

Sin embargo, una alternativa cada vez más valorada son las passphrases, frases compuestas por palabras comunes seleccionadas aleatoriamente. Este enfoque no solo permite alcanzar niveles de seguridad comparables o incluso superiores a muchas contraseñas complejas, sino que también mejora la facilidad para recordarlas. Utilizando entre 4 y 5 palabras tomadas de un diccionario lo suficientemente amplio, se puede lograr una entropía más que aceptable, sin comprometer la usabilidad.

Cabe destacar que la entropía es una buena métrica para estimar la fortaleza teórica de una contraseña o passphrase, pero no es el único factor importante. Una buena implementación también implica proteger adecuadamente las credenciales, por ejemplo, evitando almacenarlas o transmitirlas en texto plano.

En definitiva, lo más importante es generar claves de forma verdaderamente aleatoria, no reutilizarlas entre distintos servicios y asegurarse de que sean lo suficientemente largas. Ya sea que optes por contraseñas complejas o por passphrases memorables, la fórmula esencial es simple: larga + aleatoria + variada = segura.

¿Te ha parecido de utilidad el contenido?

Daniel Rodríguez

Share
Published by
Daniel Rodríguez

Recent Posts

Cómo crear un Data Lake en Azure paso a paso

El volumen de datos que las organizaciones generan y deben manejar crece día a día:…

23 horas ago

¿Por qué el azar no es tan aleatorio como parece?

Cuando escuchamos la palabra “azar”, pensamos en lo impredecible: una moneda que gira en el…

3 días ago

Detectan vulnerabilidad crítica en MLflow que permite ejecución remota de código

Una nueva vulnerabilidad crítica ha sido detectada en MLflow, la popular plataforma de código abierto…

4 días ago

Curiosidad: ¿Por qué los datos “raros” son tan valiosos?

En estadística, los valores atípicos —también llamados outliers— son esos datos que se alejan “demasiado”…

1 semana ago

Cómo ejecutar JavaScript desde Python: Guía práctica con js2py

Aunque Python y JavaScript son lenguajes muy distintos en su propósito y ecosistema, no es…

2 semanas ago

¿Media, mediana o moda en variables ordinales? Guía práctica para el análisis de datos

Cuando comenzamos un análisis de datos, uno de los primeros pasos suele ser resumir las…

2 semanas ago

This website uses cookies.