Estoy escribiendo un pequeño script de Python que periódicamente extraerá información de un servicio de terceros usando una combinación de nombre de usuario y contraseña. No necesito crear algo que sea 100% a prueba de balas (¿existe el 100%?), Pero me gustaría involucrar una buena medida de seguridad, por lo que al menos tomaría mucho tiempo para que alguien lo rompa.
Este script no tendrá una GUI y lo ejecutará periódicamente cron
, por lo que ingresar una contraseña cada vez que se ejecute para descifrar cosas no funcionará realmente, y tendré que almacenar el nombre de usuario y la contraseña en un archivo cifrado o cifrado en una base de datos SQLite, que sería preferible ya que de todos modos usaré SQLite, y es posible que necesite editar la contraseña en algún momento. Además, probablemente incluiré todo el programa en un archivo EXE, ya que es exclusivamente para Windows en este momento.
¿Cómo puedo almacenar de forma segura la combinación de nombre de usuario y contraseña para usarla periódicamente en un cron
trabajo?
fuente
Respuestas:
Recomiendo una estrategia similar a ssh-agent . Si no puede usar ssh-agent directamente, puede implementar algo así, de modo que su contraseña solo se mantenga en la RAM. El trabajo cron podría haber configurado credenciales para obtener la contraseña real del agente cada vez que se ejecuta, usarla una vez y eliminar la referencia de inmediato mediante la
del
declaración.El administrador aún tiene que ingresar la contraseña para iniciar ssh-agent, en el momento del arranque o lo que sea, pero este es un compromiso razonable que evita tener una contraseña de texto plano almacenada en cualquier lugar del disco.
fuente
La biblioteca de llaveros de Python se integra con la
CryptProtectData
API en Windows (junto con las API relevantes en Mac y Linux) que encripta 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 están encriptados 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 llavero. 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. .
fuente
keyring
Admite recuperar tanto el nombre de usuario como la contraseña?get_password
para el nombre de usuario. Aunque, creo que debería comenzar la respuesta con el ejemplo original simplificado dekeyring.set_password()
ykeyring.get_password()
keyring
no es parte de la biblioteca estándar de Pythonkeyring
Elimina con seguridad la contraseña de los registros y las contraseñas de la memoria?Después de analizar las respuestas a esta y otras preguntas relacionadas, he reunido 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 secuencia de comandos, 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 el script y ejecutar el script como ese usuario (y establecer la propiedad de los archivos del script 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ódigo criptográfico, por lo que es casi seguro que lo que he hecho podría mejorarse.
fuente
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:
Opción 1: SSH
Esta no siempre es una opción, pero probablemente sea la mejor. Su clave privada nunca se transmite a través de la red, SSH solo ejecuta cálculos matemáticos para demostrar que tiene la clave correcta.
Para que funcione, necesita lo siguiente:
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. Compruebe 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 en
EnvironmentFile
lugar deEnvironment
para cualquier secreto.Environment
Los valores pueden ser vistos por cualquier usuario consystemctl 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, encuentro que JSON y YAML son los más simples de usar, y YAML permite comentarios.
Tres cosas a considerar con los archivos de configuración:
~/.my_app
, y una opción de línea de comandos para usar una ubicación diferente.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 debe comprometerse
settings.py
con el control de código fuente, aunque es posible que desee enviar un archivo llamadosettings_template.py
que los usuarios pueden copiar y modificar.Veo algunos problemas con esta técnica:
.gitignore
reduce ese riesgo.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.
fuente
os.getenv()
. ¿Cómo deberíamos hacer esto si se comparte el código? Si otro desarrollador descarga el código, ¿cómo debe asegurarse de que las variables de entorno ya estén configuradas para él?os.getenv()
@a_sid, por lo que el código al menos se ejecutará para un usuario que no haya configurado las variables de entorno. Si no hay un buen valor predeterminado, genere un error claro cuando obtengaNone
. Aparte de eso, ponga comentarios claros en el archivo de configuración. Si entendí mal algo, le sugiero que haga una pregunta por separado.No tiene mucho sentido tratar de cifrar la contraseña: la persona a la que intentas ocultarla tiene el script Python, que tendrá el código para descifrarlo. La forma más rápida de obtener la contraseña será agregar una declaración de impresión al script de Python justo antes de que use la contraseña con el servicio de terceros.
Por lo tanto, guarde la contraseña como una cadena en el script y codifíquela en base64 para que solo leer el archivo no sea suficiente, luego llámelo un día.
fuente
Creo que lo mejor que puede hacer es proteger el archivo de secuencia de comandos y el sistema en el que se ejecuta.
Básicamente, haz lo siguiente:
fuente
Los sistemas operativos a menudo tienen soporte para proteger los datos del usuario. en el caso de Windows, parece que es http://msdn.microsoft.com/en-us/library/aa380261.aspx
puede llamar a win32 apis desde python usando http://vermeulen.ca/python-win32api.html
Por lo que tengo entendido, esto almacenará los datos para que solo se pueda acceder a ellos desde la cuenta utilizada para almacenarlos. si desea editar los datos, puede hacerlo escribiendo código para extraer, cambiar y guardar el valor.
fuente
Solía criptografía porque tenía problemas de instalar (compilación) otras bibliotecas comúnmente mencionados en mi sistema. (Win7 x64, Python 3.5)
from cryptography.fernet import Fernet key = Fernet.generate_key() cipher_suite = Fernet(key) cipher_text = cipher_suite.encrypt(b"password = scarybunny") plain_text = cipher_suite.decrypt(cipher_text)
Mi script se está ejecutando en un sistema / sala físicamente seguro. Cifro las credenciales con un "script de cifrado" en un archivo de configuración. Y luego descifrar cuando necesito usarlos. El "script de cifrado" no está en el sistema real, solo el archivo de configuración cifrado. Alguien que analiza el código puede romper fácilmente el cifrado analizando el código, pero aún puede compilarlo en un EXE si es necesario.
fuente