web-dev-qa-db-fra.com

J'ai besoin de stocker en toute sécurité un nom d'utilisateur et un mot de passe en Python, quelles sont mes options?

J'écris un petit script Python qui extraira périodiquement des informations d'un service tiers en utilisant un combo nom d'utilisateur et mot de passe. Je n'ai pas besoin de créer quelque chose qui est 100% pare-balles (fait 100 % existent même?), mais je voudrais impliquer une bonne mesure de sécurité donc au moins il faudrait beaucoup de temps à quelqu'un pour le casser.

Ce script n'aura pas d'interface graphique et sera exécuté périodiquement par cron, donc entrer un mot de passe à chaque fois qu'il est exécuté pour décrypter les choses ne fonctionnera pas vraiment, et je devrai stocker le nom d'utilisateur et le mot de passe dans soit un fichier crypté ou crypté dans une base de données SQLite, ce qui serait préférable car je vais utiliser SQLite de toute façon, et je pourrait besoin de modifier le mot de passe à un moment donné. De plus, je vais probablement envelopper l'ensemble du programme dans un EXE, car il est exclusivement pour Windows à ce stade.

Comment puis-je stocker en toute sécurité le combo nom d'utilisateur et mot de passe à utiliser périodiquement via un travail cron?

67
Naftuli Kay

Je recommande une stratégie similaire à ssh-agent . Si vous ne pouvez pas utiliser directement ssh-agent, vous pouvez implémenter quelque chose comme ça, de sorte que votre mot de passe ne soit conservé qu'en RAM. Le travail cron aurait pu configurer des informations d'identification pour obtenir le mot de passe réel de l'agent à chaque exécution, l'utiliser une fois et le déréférencer immédiatement à l'aide de l'instruction del.

L'administrateur doit toujours entrer le mot de passe pour démarrer ssh-agent, au démarrage ou autre, mais c'est un compromis raisonnable qui évite d'avoir un mot de passe en texte brut stocké n'importe où sur le disque.

17
wberry

bibliothèque de porte-clés python s'intègre à l'API CryptProtectData sous Windows (ainsi qu'aux API pertinentes sur Mac et Linux) qui chiffre les données avec les informations d'identification de connexion de l'utilisateur.

Utilisation 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

Utilisation si vous souhaitez stocker le nom d'utilisateur sur le trousseau de clés:

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)

Plus tard pour obtenir vos informations du trousseau de clés

# 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)  

Les éléments sont cryptés avec les informations d'identification du système d'exploitation de l'utilisateur, ainsi d'autres applications exécutées dans votre compte d'utilisateur pourraient accéder au mot de passe.

Pour masquer un peu cette vulnérabilité, vous pouvez crypter/masquer le mot de passe d'une certaine manière avant de le stocker sur le trousseau de clés. Bien sûr, toute personne qui ciblait votre script pourrait simplement regarder la source et comprendre comment décrypter/désobscurcir le mot de passe, mais vous empêcheriez au moins qu'une application vide tous les mots de passe du coffre-fort et obtienne le vôtre également. .

33
Dustin Wyatt

Après avoir examiné les réponses à cette question et aux questions connexes, j'ai rassemblé du code en utilisant quelques-unes des méthodes suggérées pour chiffrer et masquer les données secrètes. Ce code est spécifiquement destiné au moment où le script doit s'exécuter sans intervention de l'utilisateur (si l'utilisateur le démarre manuellement, il est préférable de le mettre dans le mot de passe et de le garder en mémoire uniquement comme le suggère la réponse à cette question). Cette méthode n'est pas super-sécurisée; fondamentalement, le script peut accéder aux informations secrètes afin que toute personne ayant un accès complet au système ait le script et ses fichiers associés et puisse y accéder. Ce que cela fait, id masque les données d'une inspection occasionnelle et laisse les fichiers de données eux-mêmes sécurisés s'ils sont examinés individuellement ou ensemble sans le script.

Ma motivation pour cela est un projet qui interroge certains de mes comptes bancaires pour surveiller les transactions - j'ai besoin qu'il s'exécute en arrière-plan sans que je ressaisisse les mots de passe toutes les minutes ou deux.

Collez simplement ce code en haut de votre script, modifiez le saltSeed puis utilisez store () retrieve () et require () dans votre code selon les besoins:

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 sécurité de cette méthode serait considérablement améliorée si les autorisations os étaient définies sur les fichiers secrets pour autoriser uniquement le script lui-même à les lire, et si le script lui-même était compilé et marqué comme exécutable uniquement (non lisible). Une partie de cela pourrait être automatisée, mais je n'ai pas pris la peine. Cela nécessiterait probablement de configurer un utilisateur pour le script et d'exécuter le script en tant qu'utilisateur (et de définir la propriété des fichiers du script pour cet utilisateur).

J'adorerais toutes suggestions, critiques ou autres points de vulnérabilité auxquels tout le monde peut penser. Je suis assez novice dans l'écriture de code cryptographique, donc ce que j'ai fait pourrait presque certainement être amélioré.

22
drodgers

Je pense que le mieux que vous puissiez faire est de protéger le fichier script et le système sur lequel il fonctionne.

Fondamentalement, procédez comme suit:

  • Utiliser les autorisations du système de fichiers (chmod 400)
  • Mot de passe fort pour le compte du propriétaire sur le système
  • Réduisez la capacité du système à être compromis (pare-feu, désactivez les services inutiles, etc.)
  • Supprimez les privilèges administratifs/root/Sudo pour ceux qui n'en ont pas besoin
6
Corey D

Il existe quelques options pour stocker les mots de passe et autres secrets qu'un programme Python doit utiliser, en particulier un programme qui doit s'exécuter en arrière-plan où il ne peut pas simplement demander à l'utilisateur de taper le mot de passe.

Problèmes à éviter:

  1. Archiver le mot de passe pour contrôler la source où les autres développeurs ou même le public peuvent le voir.
  2. D'autres utilisateurs sur le même serveur lisant le mot de passe à partir d'un fichier de configuration ou d'un code source.
  3. Avoir le mot de passe dans un fichier source où les autres peuvent le voir par-dessus votre épaule pendant que vous le modifiez.

Option 1: SSH

Ce n'est pas toujours une option, mais c'est probablement le meilleur. Votre clé privée n'est jamais transmise sur le réseau, SSH exécute simplement des calculs mathématiques pour prouver que vous avez la bonne clé.

Pour que cela fonctionne, vous avez besoin des éléments suivants:

  • La base de données ou tout ce à quoi vous accédez doit être accessible par SSH. Essayez de rechercher "SSH" plus le service auquel vous accédez. Par exemple, "ssh postgresql" . Si ce n'est pas une fonctionnalité de votre base de données, passez à l'option suivante.
  • Créez un compte pour exécuter le service qui fera des appels à la base de données et générer une clé SSH .
  • Ajoutez la clé publique au service que vous allez appeler ou créez un compte local sur ce serveur et installez-y la clé publique.

Option 2: Variables d'environnement

Celui-ci est le plus simple, il pourrait donc être un bon point de départ. Il est bien décrit dans Twelve Factor App . L'idée de base est que votre code source extrait simplement le mot de passe ou d'autres secrets des variables d'environnement, puis vous configurez ces variables d'environnement sur chaque système sur lequel vous exécutez le programme. Cela pourrait également être une bonne touche si vous utilisez des valeurs par défaut qui fonctionneront pour la plupart des développeurs. Vous devez trouver un équilibre entre cela et la sécurisation de votre logiciel par défaut.

Voici un exemple qui extrait le serveur, le nom d'utilisateur et le mot de passe des variables d'environnement.

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)

Recherchez comment définir des variables d'environnement dans votre système d'exploitation et envisagez d'exécuter le service sous son propre compte. De cette façon, vous n'avez pas de données sensibles dans les variables d'environnement lorsque vous exécutez des programmes dans votre propre compte. Lorsque vous configurez ces variables d'environnement, veillez à ce que les autres utilisateurs ne puissent pas les lire. Vérifiez les autorisations de fichier, par exemple. Bien sûr, tous les utilisateurs disposant d'une autorisation root pourront les lire, mais cela ne peut pas être aidé.

Option 3: fichiers de configuration

C'est très similaire aux variables d'environnement, mais vous lisez les secrets d'un fichier texte. Je trouve toujours les variables d'environnement plus flexibles pour des choses comme les outils de déploiement et les serveurs d'intégration continue. Si vous décidez d'utiliser un fichier de configuration, Python prend en charge plusieurs formats dans la bibliothèque standard, comme JSON , INI , netrc , et XML . Vous pouvez également trouver des des packages comme PyYAML et TOML . Personnellement, je trouve JSON et YAML les plus simples à utiliser, et YAML autorise les commentaires.

Trois choses à considérer avec les fichiers de configuration:

  1. Où est le dossier? Peut-être un emplacement par défaut comme ~/.my_app et une option de ligne de commande pour utiliser un emplacement différent.
  2. Assurez-vous que les autres utilisateurs ne peuvent pas lire le fichier.
  3. Évidemment, ne soumettez pas le fichier de configuration au code source. Vous souhaiterez peut-être valider un modèle que les utilisateurs pourront copier dans leur répertoire personnel.

Option 4: Python

Certains projets mettent juste leurs secrets dans un module Python.

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

Importez ensuite ce module pour obtenir les valeurs.

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

db_connect(db_server, db_user, db_password)

Un projet qui utilise cette technique est Django . De toute évidence, vous ne devez pas valider settings.py au contrôle de code source, bien que vous souhaitiez peut-être valider un fichier appelé settings_template.py que les utilisateurs peuvent copier et modifier.

Je vois quelques problèmes avec cette technique:

  1. Les développeurs peuvent accidentellement valider le fichier pour le contrôle de code source. L'ajouter à .gitignore réduit ce risque.
  2. Une partie de votre code n'est pas sous contrôle de code source. Si vous êtes discipliné et ne mettez ici que des chaînes et des chiffres, ce ne sera pas un problème. Si vous commencez à écrire des classes de filtres de journalisation ici, arrêtez!

Si votre projet utilise déjà cette technique, il est facile de passer aux variables d'environnement. Déplacez simplement toutes les valeurs des paramètres vers les variables d'environnement et modifiez le module Python pour lire à partir de ces variables d'environnement.

6
Don Kirkby

Il ne sert à rien d'essayer de chiffrer le mot de passe: la personne dont vous essayez de le cacher a le script Python, qui aura le code pour le déchiffrer. Le moyen le plus rapide d'obtenir le mot de passe sera d'ajouter une instruction print au script Python juste avant d'utiliser le mot de passe avec le service tiers.

Conservez donc le mot de passe sous forme de chaîne dans le script, et codez en base64 pour que la lecture du fichier ne soit pas suffisante, puis appelez-le un jour.

4
Ned Batchelder

les systèmes d'exploitation prennent souvent en charge la sécurisation des données pour l'utilisateur. dans le cas de Windows, il semble que ce soit http://msdn.Microsoft.com/en-us/library/aa380261.aspx

vous pouvez appeler win32 apis depuis python en utilisant http://vermeulen.ca/python-win32api.html

pour autant que je sache, cela stockera les données afin qu'elles ne soient accessibles qu'à partir du compte utilisé pour les stocker. si vous souhaitez modifier les données, vous pouvez le faire en écrivant du code pour extraire, modifier et enregistrer la valeur.

1
andrew cooke

J'ai utilisé Cryptographie parce que j'ai eu des problèmes pour installer (compiler) d'autres bibliothèques couramment mentionnées sur mon système. (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)

Mon script s'exécute dans un système/salle physiquement sécurisé. Je crypte les informations d'identification avec un "script de cryptage" dans un fichier de configuration. Et puis déchiffrer quand je dois les utiliser. Le "script Encrypter" n'est pas sur le vrai système, seul le fichier de configuration crypté l'est. Une personne qui analyse le code peut facilement casser le cryptage en analysant le code, mais vous pouvez toujours le compiler dans un EXE si nécessaire.

0
KRBA