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.






