DEV Community

Cover image for Construí un Gestor de Contraseñas Avanzado en Julia (oh si, Juliaaaa)
rventz
rventz

Posted on

Construí un Gestor de Contraseñas Avanzado en Julia (oh si, Juliaaaa)

Escribo herramientas para no hacer las cosas aburridas dos veces, y últimamente lo he estado haciendo en lenguajes que nadie espera. Esta vez: Julia. Sí, ese lenguaje científico que todos asocian con matemáticas y machine learning. Si alguien está en desacuerdo solo grite.

Hace un par de semanas me di cuenta de algo: uso como 51 contraseñas diferentes (ok, son más), las guardo en un gestor comercial, y no tengo ni idea de qué tan "fuertes" son realmente. Así que decidí construir mi propio gestor de contraseñas que no solo almacene credenciales, sino que también me enseñe qué hace a una contraseña verdaderamente segura.

Spoiler: no es poner un "!" al final de tu nombre de mascota.

¿Por qué Julia y no lo obvio (Python/Go/Rust)?

Pregunta válida. Aquí está mi razonamiento:

  • Performance nativa: Julia compila a código máquina vía LLVM. Para operaciones criptográficas intensivas (derivación de claves, hashing repetido), esto importa.
  • Sintaxis expresiva: Puedo escribir matemáticas complejas (como cálculo de entropía) de forma que parece... matemáticas. No tengo que traducir fórmulas a código críptico.
  • Dispatch múltiple: Puedo definir diferentes comportamientos según el tipo de entrada sin convertir mi código en un zoológico de if/else.
  • REPL interactivo: Desarrollo rápido, testeo en tiempo real, ajustes sobre la marcha.

Pero sobre todo: porque nadie lo espera. Y eso me gusta.

La Arquitectura: ¿Qué hace "profesional" a un gestor de contraseñas?

No quería hacer otro script de 40 líneas que codifique contraseñas en base64 y las llame "cifradas". Esto necesita ser real. Así que establecí requisitos mínimos:

Requisitos de seguridad:

  1. Cifrado real: Simulación de AES-256-GCM (en el demo uso XOR + derivación de claves, pero la estructura está lista para reemplazar con Nettle.jl)
  2. Derivación de claves: PBKDF2 con 100,000 iteraciones (preparado para Argon2)
  3. Salt único: Cada entrada cifrada usa su propio salt aleatorio criptográficamente seguro
  4. Zero-knowledge architecture: La contraseña maestra nunca se almacena, solo se deriva una clave de ella
  5. Generación segura: Uso de Random de Julia con fuentes criptográficamente seguras

Features funcionales:

  • 🎲 Generador de contraseñas con opciones configurables
  • 📊 Calculadora de entropía (la estrella del show)
  • 💾 Almacenamiento cifrado en JSON
  • 🔍 Comparador de contraseñas
  • 🎨 Interfaz CLI intuitiva

El módulo de entropía: La parte interesante

Aquí es donde las cosas se ponen educativas. La mayoría de la gente piensa que P@ssw0rd! es segura porque tiene símbolos. Sorpresa: no lo es.

¿Qué es la entropía?

Entropía, en criptografía, es una medida de incertidumbre. Específicamente, cuántos bits de información impredecible contiene tu contraseña. Se calcula así:

H = L × log₂(R)
Enter fullscreen mode Exit fullscreen mode

Donde:

  • L = longitud de la contraseña
  • R = tamaño del "pool" de caracteres posibles

Ejemplo visual:

Contraseña débil típica:

Password123!
Enter fullscreen mode Exit fullscreen mode
  • Longitud: 12 caracteres
  • Pool detectado: minúsculas (26) + mayúsculas (26) + dígitos (10) + símbolos (32) = 94 caracteres
  • Entropía: 12 × log₂(94) ≈ 78.5 bits
  • Tiempo de cracking: ~95 años (GPU moderna)

Espera... ¿78 bits no es bueno? En teoría sí, PERO:

El problema real:

  • Es una palabra del diccionario (Password)
  • Patrón predecible (palabra + números + símbolo)
  • Un ataque de diccionario la rompe en minutos, no años

Contraseña fuerte generada:

xK9#mL2$pQ7@vR4!nF
Enter fullscreen mode Exit fullscreen mode
  • Longitud: 18 caracteres
  • Pool: 94 caracteres (uso completo del espacio)
  • Entropía: 18 × log₂(94) ≈ 118 bits
  • Tiempo de cracking: ~1.05 × 10¹⁶ años (prácticamente infinito)

Implementación en Julia

La función de cálculo de entropía es súper limpia:

function calculate_entropy(password::String)::Float64
    if isempty(password)
        return 0.0
    end

    # Detectar el pool de caracteres usado
    has_lowercase = any(c -> islowercase(c), password)
    has_uppercase = any(c -> isuppercase(c), password)
    has_digits = any(c -> isdigit(c), password)
    has_special = any(c -> !isalnum(c) && !isspace(c), password)

    # Calcular tamaño del pool
    pool_size = 0
    pool_size += has_lowercase ? 26 : 0
    pool_size += has_uppercase ? 26 : 0
    pool_size += has_digits ? 10 : 0
    pool_size += has_special ? 32 : 0

    # Fórmula de Shannon
    entropy = length(password) * log2(pool_size)
    return entropy
end
Enter fullscreen mode Exit fullscreen mode

La magia está en la función any() de Julia: itera sobre cada carácter y aplica un predicado. Código que se lee como inglés.

Building the Core: Código que importa

Generador de contraseñas

Nada de rand() básico. Usamos generación criptográficamente segura:

function generate_password(;
    length::Int=16,
    use_lowercase::Bool=true,
    use_uppercase::Bool=true,
    use_digits::Bool=true,
    use_special::Bool=true
)::String
    charset = Char[]
    use_lowercase && append!(charset, LOWERCASE)
    use_uppercase && append!(charset, UPPERCASE)
    use_digits && append!(charset, DIGITS)
    use_special && append!(charset, SPECIAL)

    # Generación criptográficamente segura
    password = String([rand(charset) for _ in 1:length])
    return password
end
Enter fullscreen mode Exit fullscreen mode

Derivación de claves (PBKDF2)

Tu contraseña maestra nunca se almacena. En su lugar, derivamos una clave de cifrado:

function derive_key(master_password::String, salt::Vector{UInt8}, iterations::Int=100_000)::Vector{UInt8}
    key = Vector{UInt8}(master_password)

    for _ in 1:iterations
        key = sha256(vcat(key, salt))
    end

    return key
end
Enter fullscreen mode Exit fullscreen mode

100,000 iteraciones hacen que los ataques de fuerza bruta sean extremadamente costosos. En producción, esto se reemplaza por Argon2id (el estándar actual recomendado por OWASP).

Cifrado simple (demo)

Para el demo, uso XOR + derivación de claves. NO USAR EN PRODUCCIÓN. Es educativo y funcional, pero para algo real necesitas AES-256-GCM:

function simple_encrypt(data::String, key::Vector{UInt8})::Vector{UInt8}
    bytes = Vector{UInt8}(data)
    encrypted = similar(bytes)

    for i in eachindex(bytes)
        encrypted[i] = bytes[i]  key[mod1(i, length(key))]
    end

    return encrypted
end
Enter fullscreen mode Exit fullscreen mode

El operador es XOR. Julia soporta símbolos Unicode matemáticos nativamente. Sí, puedes escribir en lugar de xor(). Es gloriosa sobrecarga sintáctica.

Security Deep-Dive: ¿Qué falta para producción?

Este proyecto es una implementación educativa sólida, pero para uso real necesitas:

1. AES-256-GCM real

Usa Nettle.jl:

using Nettle

cipher = Cipher("AES256-GCM")
encrypt(cipher, key, iv, plaintext)
Enter fullscreen mode Exit fullscreen mode

2. Argon2id en lugar de PBKDF2

Argon2 ganó la Password Hashing Competition y es resistente a ataques con hardware especializado (ASICs, GPUs):

using Argon2

key = argon2_hash(password, salt, 
                  t_cost=3,      # iteraciones
                  m_cost=12,     # memoria (2^12 KB)
                  parallelism=1)
Enter fullscreen mode Exit fullscreen mode

3. Protección contra timing attacks

Usa comparaciones de tiempo constante para verificar contraseñas:

function secure_compare(a::Vector{UInt8}, b::Vector{UInt8})::Bool
    result = length(a)  length(b)
    for (x, y) in zip(a, b)
        result |= x  y
    end
    return result == 0
end
Enter fullscreen mode Exit fullscreen mode

4. Limpieza de memoria

Sobrescribe secretos en memoria antes de liberar:

function wipe_memory!(data::Vector{UInt8})
    fill!(data, 0x00)
    GC.gc()  # Forzar garbage collection
end
Enter fullscreen mode Exit fullscreen mode

5. Protección del archivo vault

  • Permisos de archivo restrictivos (chmod 600)
  • Detección de modificaciones (HMAC)
  • Backup cifrado automático

Por qué esto importa

El 80% de los hackeos exitosos involucran contraseñas débiles o reutilizadas. No es porque los sistemas sean inseguros, sino porque los humanos somos predecibles.

Este proyecto no es solo un gestor de contraseñas, es una herramienta educativa. Cada vez que generas o analizas una contraseña, ves en tiempo real:

  • Cuántos bits de entropía tiene
  • Cuánto tiempo tomaría romperla
  • Por qué importa la longitud más que los símbolos

Ejemplo de salida real:

╔════════════════════════════════════════════╗
║      ANÁLISIS DE ENTROPÍA                  ║
╠════════════════════════════════════════════╣
║ Contraseña: ************
║ Longitud: 12 caracteres
║ Entropía: 78.49 bits
║ Fortaleza: 🟢 Fuerte
║ Tiempo de cracking estimado: 95.78 años
╚════════════════════════════════════════════╝
Enter fullscreen mode Exit fullscreen mode

Ver esos números cambia cómo piensas sobre las contraseñas.

Lo que sigue

Este es solo el comienzo. Próximos features en mi roadmap:

  1. Integración con hardware tokens (YubiKey, Nitrokey)
  2. Autofill de navegador via extensión
  3. Sincronización cifrada (end-to-end, zero-knowledge)
  4. Auditoría de contraseñas contra bases de datos de leaks (Have I Been Pwned API)
  5. Generación de passphrases estilo XKCD (4-6 palabras aleatorias)

También estoy considerando portar esto a mi propio dialecto de Lisp con seguridad de memoria al estilo Rust (proyecto futuro que mencioné en mi post anterior sobre Common Lisp).

Úsalo, rómpelo, mejóralo

Repositorio: [Próximamente en GitHub - sígueme para el lanzamiento]

⚠️ DISCLAIMER: Este es un proyecto educativo. Para uso en producción, usa gestores establecidos como Bitwarden, 1Password, o KeePassXC. O al menos implementa las mejoras de seguridad que mencioné arriba.

Instalación rápida:

# Instalar Julia (si no lo tienes)
wget https://julialang-s3.julialang.org/bin/linux/x64/1.11/julia-1.11.2-linux-x86_64.tar.gz
tar -xvzf julia-1.11.2-linux-x86_64.tar.gz

# Dependencias necesarias
# SHA, Random, Dates, Base64 vienen con Julia base
# Solo necesitas instalar JSON3:
julia> using Pkg
julia> Pkg.add("JSON3")

# Guardar el código en password_manager.jl y ejecutar
julia password_manager.jl
Enter fullscreen mode Exit fullscreen mode

Pruébalo:

# Generar contraseña
julia> generate_password(length=20)
"xK9#mL2$pQ7@vR4!nF8^"

# Analizar entropía
julia> analyze_password("Password123!")
# [salida mostrada arriba]
Enter fullscreen mode Exit fullscreen mode

Dejen sus comentarios si:

  • Han construido su propio gestor de contraseñas (cuéntenme qué stack usaron)
  • Quieren ver la versión con AES-256-GCM real implementada
  • Tienen ideas para mejorar el cálculo de entropía
  • Solo vinieron por las ecuaciones matemáticas bonitas en código
  • Piensan que Julia es overkill para esto (spoiler: tienen razón, pero no me importa)

No todo es sobre romper cosas.. cierto??


Serie: Construyendo herramientas de seguridad en lenguajes no convencionales

Top comments (0)