Saltar al contenido

Necesito almacenar de forma segura un nombre de usuario y una contraseña en Python, ¿cuáles son mis opciones?

Hola usuario de nuestra página web, hallamos la respuesta a tu búsqueda, desplázate y la hallarás más abajo.

Solución:

La biblioteca de llaveros de Python se integra con CryptProtectData API en Windows (junto con las API relevantes en Mac y Linux) que cifra los datos con las credenciales de inicio de sesión del usuario.

Uso simple:

import keyring

# the service is just a namespace for your app
service_id = 'IM_YOUR_APP!'

keyring.set_password(service_id, 'dustin', 'my secret password')
password = keyring.get_password(service_id, 'dustin') # retrieve password

Uso si desea almacenar el nombre de usuario en el llavero:

import keyring

MAGIC_USERNAME_KEY = 'im_the_magic_username_key'

# the service is just a namespace for your app
service_id = 'IM_YOUR_APP!'  

username = 'dustin'

# save password
keyring.set_password(service_id, username, "password")

# optionally, abuse `set_password` to save username onto keyring
# we're just using some known magic string in the username field
keyring.set_password(service_id, MAGIC_USERNAME_KEY, username)

Más tarde para obtener su información del llavero

# again, abusing `get_password` to get the username.
# after all, the keyring is just a key-value store
username = keyring.get_password(service_id, MAGIC_USERNAME_KEY)
password = keyring.get_password(service_id, username)  

Los elementos se cifran con las credenciales del sistema operativo del usuario, por lo que otras aplicaciones que se ejecutan en su cuenta de usuario podrían acceder a la contraseña.

Para ocultar un poco esa vulnerabilidad, puede cifrar / ocultar la contraseña de alguna manera antes de almacenarla en el anillo de claves. Por supuesto, cualquiera que tuviera como objetivo su secuencia de comandos solo podría mirar la fuente y descubrir cómo desencriptar / desencriptar la contraseña, pero al menos evitaría que alguna aplicación elimine todas las contraseñas en la bóveda y obtenga la suya también. .

Hay algunas opciones para almacenar contraseñas y otros secretos que un programa Python necesita usar, particularmente un programa que necesita ejecutarse en segundo plano donde no puede simplemente pedirle al usuario que escriba la contraseña.

Problemas a evitar:

  1. Verificando la contraseña en el control de fuente donde otros desarrolladores o incluso el público pueden verla.
  2. Otros usuarios del mismo servidor que leen la contraseña de un archivo de configuración o código fuente.
  3. Tener la contraseña en un archivo de origen donde otros puedan verla por encima del hombro mientras la edita.

Opción 1: SSH

Esta no siempre es una opción, pero probablemente sea la mejor. Tu privado key nunca se transmite a través de la red, SSH solo ejecuta cálculos matemáticos para demostrar que tiene el derecho key.

Para que funcione, necesita lo siguiente:

  • La base de datos o lo que sea que esté accediendo debe ser accesible por SSH. Intente buscar “SSH” más cualquier servicio al que esté accediendo. Por ejemplo, “ssh postgresql”. Si esta no es una característica de su base de datos, continúe con la siguiente opción.
  • Cree una cuenta para ejecutar el servicio que hará llamadas a la base de datos y generará un SSH key.
  • O agregue el público key al servicio al que va a llamar, o cree una cuenta local en ese servidor e instale el key allí.

Opción 2: Variables de entorno

Este es el más simple, por lo que podría ser un buen lugar para comenzar. Se describe bien en la aplicación Twelve Factor. La idea básica es que su código fuente simplemente extrae la contraseña u otros secretos de las variables de entorno, y luego configura esas variables de entorno en cada sistema donde ejecuta el programa. También podría ser un buen toque si usa valores predeterminados que funcionarán para la mayoría de los desarrolladores. Debe equilibrar eso con hacer que su software sea “seguro por defecto”.

A continuación, se muestra un ejemplo que extrae el servidor, el nombre de usuario y la contraseña de las variables de entorno.

import os

server = os.getenv('MY_APP_DB_SERVER', 'localhost')
user = os.getenv('MY_APP_DB_USER', 'myapp')
password = os.getenv('MY_APP_DB_PASSWORD', '')

db_connect(server, user, password)

Busque cómo establecer variables de entorno en su sistema operativo y considere ejecutar el servicio con su propia cuenta. De esa manera, no tendrá datos confidenciales en las variables de entorno cuando ejecute programas en su propia cuenta. Cuando configure esas variables de entorno, tenga especial cuidado de que otros usuarios no puedan leerlas. Verifique los permisos de los archivos, por ejemplo. Por supuesto, cualquier usuario con permiso de root podrá leerlos, pero eso no se puede evitar. Si está usando systemd, mire la unidad de servicio y tenga cuidado de usar EnvironmentFile en lugar de Environment por cualquier secreto. Environment Los valores pueden ser vistos por cualquier usuario con systemctl show.

Opción 3: archivos de configuración

Esto es muy similar a las variables de entorno, pero lee los secretos de un archivo de texto. Todavía encuentro las variables de entorno más flexibles para cosas como herramientas de implementación y servidores de integración continua. Si decide utilizar un archivo de configuración, Python admite varios formatos en la biblioteca estándar, como JSON, INI, netrc y XML. También puede encontrar paquetes externos como PyYAML y TOML. Personalmente, considero que JSON y YAML son los más sencillos de usar, y YAML permite comentarios.

Tres cosas a considerar con los archivos de configuración:

  1. ¿Dónde está el archivo? Tal vez una ubicación predeterminada como ~/.my_appy una opción de línea de comandos para usar una ubicación diferente.
  2. Asegúrese de que otros usuarios no puedan leer el archivo.
  3. Obviamente, no confíe el archivo de configuración al código fuente. Es posible que desee enviar una plantilla que los usuarios puedan copiar en su directorio de inicio.

Opción 4: Módulo Python

Algunos proyectos simplemente ponen sus secretos directamente en un módulo de Python.

# settings.py
db_server = 'dbhost1'
db_user = 'my_app'
db_password = 'correcthorsebatterystaple'

Luego importe ese módulo para obtener los valores.

# my_app.py
from settings import db_server, db_user, db_password

db_connect(db_server, db_user, db_password)

Un proyecto que utiliza esta técnica es Django. Obviamente, no deberías comprometerte settings.py al control de código fuente, aunque es posible que desee enviar un archivo llamado settings_template.py que los usuarios pueden copiar y modificar.

Veo algunos problemas con esta técnica:

  1. Los desarrolladores pueden enviar accidentalmente el archivo al control de código fuente. Agregándolo a .gitignore reduce ese riesgo.
  2. Parte de su código no está bajo control de fuente. Si eres disciplinado y solo pones cadenas y números aquí, eso no será un problema. Si comienza a escribir clases de filtro de registro aquí, ¡deténgase!

Si su proyecto ya utiliza esta técnica, es fácil realizar la transición a las variables de entorno. Simplemente mueva todos los valores de configuración a las variables de entorno y cambie el módulo de Python para leer esas variables de entorno.

Después de analizar las respuestas a esta y otras preguntas relacionadas, reuní un código utilizando algunos de los métodos sugeridos para encriptar y ocultar datos secretos. Este código es específicamente para cuando el script debe ejecutarse sin la intervención del usuario (si el usuario lo inicia manualmente, es mejor que ingrese la contraseña y solo la guarde en la memoria como sugiere la respuesta a esta pregunta). Este método no es muy seguro; Básicamente, el script puede acceder a la información secreta para que cualquier persona que tenga acceso completo al sistema tenga el script y sus archivos asociados y pueda acceder a ellos. Lo que hace esto id oculta los datos de una inspección casual y deja los archivos de datos seguros si se examinan individualmente o juntos sin el script.

Mi motivación para esto es un proyecto que sondea algunas de mis cuentas bancarias para monitorear las transacciones; necesito que se ejecute en segundo plano sin tener que volver a ingresar las contraseñas cada minuto o dos.

Simplemente pegue este código en la parte superior de su script, cambie saltSeed y luego use store () retrieve () y require () en su código según sea necesario:

from getpass import getpass
from pbkdf2 import PBKDF2
from Crypto.Cipher import AES
import os
import base64
import pickle


### Settings ###

saltSeed = 'mkhgts465wef4fwtdd' # MAKE THIS YOUR OWN RANDOM STRING

PASSPHRASE_FILE = './secret.p'
SECRETSDB_FILE = './secrets'
PASSPHRASE_SIZE = 64 # 512-bit passphrase
KEY_SIZE = 32 # 256-bit key
BLOCK_SIZE = 16  # 16-bit blocks
IV_SIZE = 16 # 128-bits to initialise
SALT_SIZE = 8 # 64-bits of salt


### System Functions ###

def getSaltForKey(key):
    return PBKDF2(key, saltSeed).read(SALT_SIZE) # Salt is generated as the hash of the key with it's own salt acting like a seed value

def encrypt(plaintext, salt):
    ''' Pad plaintext, then encrypt it with a new, randomly initialised cipher. Will not preserve trailing whitespace in plaintext!'''

    # Initialise Cipher Randomly
    initVector = os.urandom(IV_SIZE)

    # Prepare cipher key:
    key = PBKDF2(passphrase, salt).read(KEY_SIZE)

    cipher = AES.new(key, AES.MODE_CBC, initVector) # Create cipher

    return initVector + cipher.encrypt(plaintext + ' '*(BLOCK_SIZE - (len(plaintext) % BLOCK_SIZE))) # Pad and encrypt

def decrypt(ciphertext, salt):
    ''' Reconstruct the cipher object and decrypt. Will not preserve trailing whitespace in the retrieved value!'''

    # Prepare cipher key:
    key = PBKDF2(passphrase, salt).read(KEY_SIZE)

    # Extract IV:
    initVector = ciphertext[:IV_SIZE]
    ciphertext = ciphertext[IV_SIZE:]

    cipher = AES.new(key, AES.MODE_CBC, initVector) # Reconstruct cipher (IV isn't needed for edecryption so is set to zeros)

    return cipher.decrypt(ciphertext).rstrip(' ') # Decrypt and depad


### User Functions ###

def store(key, value):
    ''' Sore key-value pair safely and save to disk.'''
    global db

    db[key] = encrypt(value, getSaltForKey(key))
    with open(SECRETSDB_FILE, 'w') as f:
        pickle.dump(db, f)

def retrieve(key):
    ''' Fetch key-value pair.'''
    return decrypt(db[key], getSaltForKey(key))

def require(key):
    ''' Test if key is stored, if not, prompt the user for it while hiding their input from shoulder-surfers.'''
    if not key in db: store(key, getpass('Please enter a value for "%s":' % key))


### Setup ###

# Aquire passphrase:
try:
    with open(PASSPHRASE_FILE) as f:
        passphrase = f.read()
    if len(passphrase) == 0: raise IOError
except IOError:
    with open(PASSPHRASE_FILE, 'w') as f:
        passphrase = os.urandom(PASSPHRASE_SIZE) # Random passphrase
        f.write(base64.b64encode(passphrase))

        try: os.remove(SECRETSDB_FILE) # If the passphrase has to be regenerated, then the old secrets file is irretrievable and should be removed
        except: pass
else:
    passphrase = base64.b64decode(passphrase) # Decode if loaded from already extant file

# Load or create secrets database:
try:
    with open(SECRETSDB_FILE) as f:
        db = pickle.load(f)
    if db == : raise IOError
except (IOError, EOFError):
    db = 
    with open(SECRETSDB_FILE, 'w') as f:
        pickle.dump(db, f)

### Test (put your code here) ###
require('id')
require('password1')
require('password2')
print
print 'Stored Data:'
for key in db:
    print key, retrieve(key) # decode values on demand to avoid exposing the whole database in memory
    # DO STUFF

La seguridad de este método mejoraría significativamente si los permisos del sistema operativo se establecieran en los archivos secretos para permitir que el propio script solo los lea, y si el propio script se compiló y marcó como ejecutable solamente (no legible). Algo de eso podría automatizarse, pero no me he molestado. Probablemente requeriría configurar un usuario para la secuencia de comandos y ejecutar la secuencia de comandos como ese usuario (y establecer la propiedad de los archivos de la secuencia de comandos para ese usuario).

Me encantaría cualquier sugerencia, crítica u otros puntos de vulnerabilidad que cualquiera pueda pensar. Soy bastante nuevo en la escritura de códigos criptográficos, por lo que es casi seguro que lo que he hecho podría mejorarse.

Reseñas y puntuaciones de la guía

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *