Cómo crear un gestor de contraseñas sencillo con Python

Publicado el: 11/07/2026
Tempo de leitura: 5 minutos
Gerenciador de senhas simples desenvolvido com Python

Un gestor de contraseñas almacena credenciales de forma organizada y evita reutilizar la misma contraseña en varios servicios. Construir uno con Python es un proyecto educativo útil para practicar cifrado, archivos, validación y menús de terminal. Sin embargo, un ejemplo casero no debe presentarse como sustituto automático de un gestor profesional auditado. La seguridad depende de muchos detalles que van más allá de ocultar texto.

En este proyecto utilizaremos Fernet, incluido en la biblioteca cryptography, para cifrado autenticado. También aplicaremos el módulo secrets de Python para generar contraseñas y pathlib para manejar archivos.

Qué debe proteger el programa

El archivo cifrado puede contener servicios, usuarios y contraseñas. La clave de cifrado debe mantenerse separada. Si un atacante obtiene tanto el archivo como la clave, podrá descifrarlo. Por eso un diseño real suele derivar la clave desde una contraseña maestra usando una función lenta como scrypt, Argon2 o PBKDF2, además de un salt aleatorio.

Para mantener el ejemplo comprensible, primero crearemos una versión con una clave almacenada en un archivo separado y después explicaremos cómo mejorarla.

Preparar el entorno

python -m venv .venv
# Windows PowerShell
.venv\Scripts\Activate.ps1
# macOS o Linux
source .venv/bin/activate

python -m pip install cryptography

No instales la dependencia globalmente si puedes evitarlo. Guarda las versiones en un archivo de proyecto para reproducir el entorno.

Estructura del proyecto

gestor/
├── gestor.py
├── datos.enc
├── clave.key
└── .gitignore

Añade clave.key, datos.enc y cualquier archivo .env al .gitignore. El repositorio no debe contener secretos reales.

Generar una clave Fernet

from pathlib import Path
from cryptography.fernet import Fernet

RUTA_CLAVE = Path("clave.key")


def crear_clave() -> None:
    if RUTA_CLAVE.exists():
        raise FileExistsError("La clave ya existe")
    RUTA_CLAVE.write_bytes(Fernet.generate_key())

crear_clave()

Ejecuta esta operación una sola vez. Sobrescribir la clave impediría recuperar los datos cifrados anteriormente.

Cargar el cifrador

def cargar_fernet() -> Fernet:
    if not RUTA_CLAVE.exists():
        raise FileNotFoundError("No se encontró la clave")
    return Fernet(RUTA_CLAVE.read_bytes())

Comprueba los errores de forma explícita. Un programa no debe crear silenciosamente una clave nueva cuando falta la original, porque parecería que la base de datos está dañada.

Modelo de datos

Podemos guardar registros como JSON antes de cifrarlos.

import json

registros = [
    {
        "servicio": "correo",
        "usuario": "[email protected]",
        "contrasena": "ejemplo-no-real"
    }
]

texto = json.dumps(registros, ensure_ascii=False).encode("utf-8")

JSON facilita lectura y escritura, pero no aporta seguridad por sí mismo. Todo el contenido debe cifrarse antes de guardarlo.

Cifrar y guardar

RUTA_DATOS = Path("datos.enc")


def guardar(registros: list[dict[str, str]]) -> None:
    fernet = cargar_fernet()
    contenido = json.dumps(registros, ensure_ascii=False).encode("utf-8")
    cifrado = fernet.encrypt(contenido)
    RUTA_DATOS.write_bytes(cifrado)

Fernet incluye autenticación. Si alguien modifica el archivo, el descifrado fallará en lugar de devolver datos alterados sin advertencia.

Descifrar y cargar

from cryptography.fernet import InvalidToken


def cargar() -> list[dict[str, str]]:
    if not RUTA_DATOS.exists():
        return []

    fernet = cargar_fernet()
    try:
        contenido = fernet.decrypt(RUTA_DATOS.read_bytes())
    except InvalidToken as error:
        raise RuntimeError("Clave incorrecta o archivo dañado") from error

    datos = json.loads(contenido.decode("utf-8"))
    if not isinstance(datos, list):
        raise ValueError("Formato de datos inválido")
    return datos

No muestres detalles internos innecesarios al usuario. Registra el error de forma segura sin incluir contraseñas ni contenido descifrado.

Generar contraseñas seguras

import secrets
import string


def generar_contrasena(longitud: int = 20) -> str:
    if longitud < 16:
        raise ValueError("La longitud mínima es 16")

    simbolos = "!@#$%&*+-_"
    alfabeto = string.ascii_letters + string.digits + simbolos

    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 simbolos for c in valor)
        ):
            return valor

También puedes estudiar el proyecto completo de generador seguro de contraseñas. La longitud aporta más seguridad que reglas excesivamente complicadas.

Añadir un registro

def agregar_registro(
    registros: list[dict[str, str]],
    servicio: str,
    usuario: str,
    contrasena: str,
) -> None:
    servicio = servicio.strip()
    usuario = usuario.strip()

    if not servicio or not usuario or not contrasena:
        raise ValueError("Todos los campos son obligatorios")

    registros.append({
        "servicio": servicio,
        "usuario": usuario,
        "contrasena": contrasena,
    })

Evita permitir servicios vacíos y decide cómo gestionar duplicados. Puede ser mejor actualizar un registro existente que crear varias entradas indistinguibles.

Buscar registros

def buscar(registros, termino):
    termino = termino.strip().lower()
    return [
        registro
        for registro in registros
        if termino in registro["servicio"].lower()
        or termino in registro["usuario"].lower()
    ]

No imprimas todas las contraseñas de manera predeterminada. Muestra servicio y usuario, y revela la contraseña únicamente después de una acción explícita.

Leer la contraseña maestra sin mostrarla

El módulo getpass evita que la entrada aparezca en el terminal:

from getpass import getpass

maestra = getpass("Contraseña maestra: ")

La guía para leer contraseñas de forma segura en el terminal explica limitaciones y validación. Recuerda que ocultar la entrada no protege contra malware, keyloggers o un sistema comprometido.

Derivar la clave desde una contraseña maestra

Guardar clave.key junto al archivo cifrado es cómodo, pero ofrece poca protección si ambos se copian. Una mejora consiste en derivar la clave mediante scrypt y un salt aleatorio.

import base64
import os
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt


def derivar_clave(contrasena: str, salt: bytes) -> bytes:
    kdf = Scrypt(
        salt=salt,
        length=32,
        n=2**14,
        r=8,
        p=1,
    )
    clave = kdf.derive(contrasena.encode("utf-8"))
    return base64.urlsafe_b64encode(clave)

salt = os.urandom(16)

El salt no necesita ser secreto y puede almacenarse junto al archivo. Los parámetros deben equilibrar seguridad y rendimiento. Un sistema real también necesita migración de parámetros y manejo cuidadoso de la memoria.

No confundir cifrado con hash

Las contraseñas de usuarios de una aplicación normalmente se almacenan mediante hash lento, no cifrado reversible. Un gestor necesita recuperar la contraseña original, por eso utiliza cifrado. Para autenticación, consulta la guía de hash de contraseñas con bcrypt.

Crear un menú de terminal

def mostrar_menu():
    print("1. Añadir")
    print("2. Buscar")
    print("3. Generar contraseña")
    print("4. Salir")

while True:
    mostrar_menu()
    opcion = input("Opción: ").strip()

    if opcion == "1":
        pass
    elif opcion == "2":
        pass
    elif opcion == "3":
        print(generar_contrasena())
    elif opcion == "4":
        break
    else:
        print("Opción inválida")

La guía de menús interactivos en Python presenta una estructura más modular con funciones y diccionarios.

Copias de seguridad

Una copia debe incluir el archivo cifrado y el material necesario para derivar o recuperar la clave. Guarda ambos con controles de acceso adecuados, pero evita colocarlos juntos en un lugar público. Prueba la restauración; una copia que nunca se ha restaurado no está verificada.

Bloqueo y concurrencia

Si dos procesos escriben el archivo al mismo tiempo, pueden sobrescribir cambios. Un proyecto más completo necesita bloqueo de archivo, escritura atómica en un archivo temporal y reemplazo final. También debería conservar una copia anterior en caso de interrupción.

Portapapeles

Copiar contraseñas al portapapeles mejora la comodidad, pero crea otro riesgo. Limpia el portapapeles después de un tiempo razonable y advierte al usuario. No registres ni muestres el valor durante la operación.

Limitaciones del proyecto

Este ejemplo no incluye auditoría externa, sincronización segura, protección de memoria, autofill, detección de phishing, hardware seguro ni recuperación avanzada. Por eso es adecuado para aprender, no para almacenar inmediatamente todas las credenciales críticas de una empresa.

Errores frecuentes

Los problemas más graves son guardar la clave junto al código, subir archivos al repositorio, inventar un algoritmo criptográfico, reutilizar una contraseña maestra débil, registrar secretos y sobrescribir la clave. También es peligroso pensar que codificación Base64 equivale a cifrado.

Fuentes confiables

La documentación oficial de Fernet en cryptography explica cifrado autenticado, claves y rotación. La guía de gestión de secretos de OWASP cubre almacenamiento, acceso, rotación y auditoría.

Conclusión

Un gestor sencillo con Python puede cifrar registros, generar contraseñas y practicar un diseño más seguro. Mantén la clave separada, usa primitivas auditadas, valida datos y evita exponer secretos. Para uso crítico, prefiere productos profesionales revisados y utiliza este proyecto como laboratorio para comprender los componentes que hacen posible su seguridad.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Geração automática de QR Code usando Python
    Proyectos
    Foto de perfil de Leandro Hirt da Academify

    Cómo generar códigos QR con Python paso a paso

    Genera códigos QR con Python y qrcode: tamaños, corrección de errores, colores, logotipos, lotes CSV, rutas seguras y validación.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Sistema de login simples desenvolvido com Python
    Proyectos
    Foto de perfil de Leandro Hirt da Academify

    Sistema de login en Python con archivos TXT

    Crea un sistema de login en Python con archivos TXT, getpass, hashes, PBKDF2, validación y advertencias de seguridad.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Projeto clássico do jogo Pong desenvolvido com Python
    Proyectos
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear el juego Pong en Python con Turtle

    Crea un juego Pong en Python con Turtle: ventana, paletas, pelota, teclado, colisiones, marcador, velocidad y código completo.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Criação de jogos para iniciantes usando Pygame em Python
    Proyectos
    Foto de perfil de Leandro Hirt da Academify

    Pygame para principiantes: crea tu primer juego

    Aprende Pygame desde cero: instalación, ventana, eventos, movimiento, delta time, colisiones, puntuación, imágenes, sonido y sprites.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Ilustração de uma cobra com o logo do Python centralizado
    Proyectos
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear el juego Snake en Python con Turtle

    Crea el juego Snake en Python con Turtle: movimiento, comida, crecimiento, puntuación, colisiones, controles y mejoras de dificultad.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Chatbot simples em Python
    Proyectos
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear un chatbot simple con Python

    Crea un chatbot simple con Python usando reglas, normalización de texto, intents, JSON, memoria temporal, pruebas y una arquitectura reutilizable.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026