Cómo leer contraseñas de forma segura en el terminal con Python

Publicado el: 11/07/2026
Tempo de leitura: 5 minutos
Leitura segura de senhas no terminal usando Python

Leer una contraseña desde el terminal parece una tarea sencilla, pero utilizar input() muestra cada carácter en la pantalla y puede dejar información sensible visible para otras personas, grabaciones o historiales de sesión. Python incluye el módulo getpass, diseñado para solicitar secretos sin mostrarlos mientras el usuario escribe. Esta solución mejora la privacidad de la entrada, aunque no sustituye el almacenamiento seguro, el cifrado de conexiones ni una política de autenticación completa.

Antes de continuar conviene revisar la función input(), el manejo de excepciones, las funciones en Python, la guía de hashing de contraseñas con bcrypt y el uso de variables de entorno con python-dotenv.

Las referencias externas principales son la documentación oficial del módulo getpass y la guía de autenticación de OWASP, que explica controles adicionales para sistemas reales.

Por qué input() no es apropiado para contraseñas

usuario = input("Usuario: ")
contrasena = input("Contraseña: ")

Este código funciona, pero la contraseña queda visible. Al compartir pantalla, grabar una demostración o trabajar en un espacio público, el secreto puede quedar expuesto. Además, algunas terminales conservan texto copiado o permiten revisar el contenido visual anterior.

Solicitar una contraseña con getpass

from getpass import getpass

usuario = input("Usuario: ").strip()
contrasena = getpass("Contraseña: ")

print(f"Usuario recibido: {usuario}")

Mientras el usuario escribe, normalmente no aparece ningún carácter. Esto es diferente de mostrar asteriscos: la implementación estándar suele ocultar completamente la entrada. No imprimas después la variable ni la incluyas en mensajes de depuración.

Crear una función de lectura

from getpass import getpass


def leer_contrasena(mensaje="Contraseña: "):
    valor = getpass(mensaje)
    if not valor:
        raise ValueError("La contraseña no puede estar vacía")
    return valor

Separar la lectura permite reutilizar la validación y probar la lógica que procesa el resultado. La función devuelve un string; no convierte ni cifra automáticamente su contenido.

Confirmar la contraseña

def solicitar_nueva_contrasena():
    primera = getpass("Nueva contraseña: ")
    segunda = getpass("Repite la contraseña: ")

    if primera != segunda:
        raise ValueError("Las contraseñas no coinciden")
    if not primera:
        raise ValueError("La contraseña no puede estar vacía")
    return primera

La comparación confirma que el usuario no cometió un error de escritura. En una interfaz real, limita los intentos y proporciona mensajes claros sin revelar detalles innecesarios.

Validar longitud y calidad básica

def validar_contrasena(valor):
    errores = []

    if len(valor) < 12:
        errores.append("Debe tener al menos 12 caracteres")
    if valor.lower() == valor:
        errores.append("Debe incluir una mayúscula")
    if valor.upper() == valor:
        errores.append("Debe incluir una minúscula")
    if not any(c.isdigit() for c in valor):
        errores.append("Debe incluir un número")

    return errores

Estas reglas son un ejemplo educativo. Las políticas modernas suelen favorecer contraseñas largas y permitir frases de paso, en lugar de imponer combinaciones demasiado rígidas. También deben bloquearse contraseñas comprometidas y aplicarse límites de intentos.

Crear un flujo interactivo robusto

def crear_contrasena(max_intentos=3):
    for intento in range(1, max_intentos + 1):
        primera = getpass("Nueva contraseña: ")
        segunda = getpass("Confirmación: ")

        if primera != segunda:
            print("Las contraseñas no coinciden")
            continue

        errores = validar_contrasena(primera)
        if errores:
            print("No cumple los requisitos:")
            for error in errores:
                print(f"- {error}")
            continue

        return primera

    raise RuntimeError("Se superó el número de intentos")

El límite impide un bucle sin fin. No incluyas la contraseña en el mensaje de error, logs o excepciones.

No almacenar contraseñas en texto plano

Ocultar la entrada solo protege el momento de escritura. Si guardas el valor directamente, cualquier persona con acceso al archivo podrá leerlo:

# No recomendado
with open("usuarios.txt", "w", encoding="utf-8") as archivo:
    archivo.write(contrasena)

Para autenticar usuarios, almacena un hash lento y diseñado para contraseñas, como bcrypt, Argon2 o scrypt. Un hash no es cifrado reversible: el sistema verifica si una contraseña produce un resultado compatible, sin necesitar recuperar el texto original.

Ejemplo con bcrypt

import bcrypt
from getpass import getpass

contrasena = getpass("Nueva contraseña: ").encode("utf-8")
hash_guardado = bcrypt.hashpw(contrasena, bcrypt.gensalt())

intento = getpass("Contraseña para acceder: ").encode("utf-8")

if bcrypt.checkpw(intento, hash_guardado):
    print("Acceso permitido")
else:
    print("Credenciales inválidas")

No uses SHA-256 directamente para contraseñas, porque es demasiado rápido y facilita ataques masivos. Una biblioteca especializada gestiona el salt y el coste computacional.

Evitar secretos en argumentos de línea de comandos

# Evita ejecutar algo como:
# python programa.py --password mi_secreto

Los argumentos pueden aparecer en el historial del shell o en la lista de procesos. Para un secreto introducido por una persona, getpass es preferible. Para credenciales de servicios, usa un gestor de secretos o variables de entorno protegidas, según el contexto.

Usar getuser para conocer el usuario del sistema

from getpass import getuser

print(getuser())

getuser() intenta obtener el nombre del usuario del entorno. No lo trates como autenticación: es información del sistema local y puede no representar una identidad verificada.

Manejar terminales incompatibles

En algunos entornos interactivos, notebooks, IDEs o terminales redirigidas, getpass puede emitir una advertencia y no ocultar la entrada como se espera. La documentación oficial describe GetPassWarning. Puedes tratarla explícitamente:

from getpass import GetPassWarning, getpass
import warnings

with warnings.catch_warnings():
    warnings.simplefilter("error", GetPassWarning)
    try:
        secreto = getpass("Secreto: ")
    except GetPassWarning:
        raise RuntimeError("La terminal no permite ocultar la entrada")

Es mejor detener el proceso que solicitar un secreto de forma visible sin avisar.

No registrar el contenido sensible

import logging

logging.info("Intento de autenticación recibido")
# Nunca hagas: logging.info("Contraseña: %s", contrasena)

Los logs suelen copiarse a servidores, sistemas de observabilidad y copias de seguridad. Incluso un entorno de desarrollo puede conservarlos durante meses.

Reducir la permanencia en memoria

En Python, los strings son inmutables y no existe una garantía sencilla de borrar inmediatamente todas las copias de una contraseña en memoria. Evita duplicar el valor, formatearlo en otros strings o mantenerlo en variables globales. Úsalo durante el menor tiempo posible y delega la autenticación a bibliotecas confiables.

Ejemplo de inicio de sesión local

import bcrypt
from getpass import getpass

HASH_ESPERADO = b"$2b$12$..."  # Cargar desde almacenamiento seguro


def autenticar():
    usuario = input("Usuario: ").strip()
    intento = getpass("Contraseña: ").encode("utf-8")

    usuario_valido = usuario == "admin"
    contrasena_valida = bcrypt.checkpw(intento, HASH_ESPERADO)

    if usuario_valido and contrasena_valida:
        print("Acceso permitido")
        return True

    print("Credenciales inválidas")
    return False

El mensaje no indica si falló el usuario o la contraseña, lo que reduce información útil para un atacante. Un sistema real necesita protección contra fuerza bruta, sesiones seguras, recuperación de cuenta y autenticación multifactor.

Pruebas sin escribir contraseñas reales

La validación debe probarse con valores ficticios. Separa la entrada de la lógica:

def credencial_valida(usuario, intento, hash_esperado):
    return usuario == "admin" and bcrypt.checkpw(
        intento.encode("utf-8"),
        hash_esperado,
    )

Así los tests pueden usar hashes creados específicamente para pruebas. Nunca copies credenciales de producción en el repositorio.

Errores frecuentes

Los problemas más comunes son usar input(), imprimir la contraseña para depurar, guardarla en texto plano, enviarla como argumento del terminal, registrar excepciones con datos sensibles y creer que ocultar la escritura resuelve toda la seguridad. También es un error inventar un algoritmo de hash propio.

Conclusión

getpass es la herramienta estándar para leer contraseñas sin mostrarlas en el terminal. Combínala con validación, límites de intentos, hashing especializado y mensajes de error discretos. Mantén los secretos fuera de logs, argumentos y repositorios. La entrada oculta es solo una capa; la seguridad real depende de todo el ciclo de autenticación y almacenamiento.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Exemplo de testes unitários em Python com código de unittest para validação automatizada
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Tests unitarios en Python: unittest, pytest y mocks

    Aprende tests unitarios en Python con unittest, pytest, fixtures, parametrización, mocks, cobertura y buenas prácticas para evitar regresiones.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Proteção de API Flask usando autenticação JWT em Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Cómo proteger una API Flask con JWT

    Protege una API Flask con JWT, access y refresh tokens, bcrypt, roles, revocación, variables de entorno, HTTPS y pruebas de

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Enums en Python: evita valores mágicos y errores

    Aprende enums en Python con Enum, IntEnum, StrEnum, auto, unique, Flag, validación, JSON, bases de datos y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Medição de tempo de execução de código Python com timeit
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    timeit en Python: mide el rendimiento correctamente

    Aprende timeit en Python para medir funciones, usar repeat y Timer, comparar implementaciones, reducir ruido y evitar benchmarks engañosos.

    Ler mais

    Tempo de leitura: 6 minutos
    11/07/2026
    Uso de dataclasses para simplificar classes em Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Dataclasses en Python: clases de datos más limpias

    Aprende dataclasses en Python: campos, valores predeterminados, field, frozen, order, __post_init__, herencia, asdict y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Criação de hashes seguros para senhas usando Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Hash de contraseñas con bcrypt en Python

    Aprende bcrypt en Python para almacenar contraseñas: salt, coste, hash, verificación, rehash, límites, errores y recomendaciones de seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026