Devo memorizzare in modo sicuro un nome utente e una password in Python, quali sono le mie opzioni? [chiuso]


95

Sto scrivendo un piccolo script Python che estrarrà periodicamente le informazioni da un servizio di terze parti utilizzando una combinazione di nome utente e password. Non ho bisogno di creare qualcosa che sia al 100% a prova di proiettile (esiste anche al 100%?), Ma vorrei coinvolgere una buona misura di sicurezza così come minimo ci vorrebbe molto tempo prima che qualcuno lo rompa.

Questo script non avrà una GUI e verrà eseguito periodicamente da cron, quindi inserire una password ogni volta che viene eseguito per decrittografare le cose non funzionerà davvero e dovrò memorizzare il nome utente e la password in un file crittografato o crittografato in un database SQLite, che sarebbe preferibile in quanto userò comunque SQLite e potrei aver bisogno di modificare la password ad un certo punto. Inoltre, probabilmente avvolgerò l'intero programma in un EXE, poiché a questo punto è esclusivamente per Windows.

Come posso memorizzare in modo sicuro la combinazione di nome utente e password da utilizzare periodicamente tramite un cronlavoro?


Risposte:


19

Raccomando una strategia simile a ssh-agent . Se non puoi usare direttamente ssh-agent, potresti implementare qualcosa di simile, in modo che la tua password sia conservata solo nella RAM. Il cron job potrebbe aver configurato le credenziali per ottenere la password effettiva dall'agente ogni volta che viene eseguito, utilizzarla una volta e de-referenziarla immediatamente utilizzando l' delistruzione.

L'amministratore deve ancora inserire la password per avviare ssh-agent, all'avvio o qualsiasi altra cosa, ma questo è un ragionevole compromesso che evita di avere una password di testo normale memorizzata ovunque sul disco.


2
+1, ha molto senso. Potrei sempre creare un'interfaccia utente che essenzialmente chiede all'utente la sua password all'avvio, in questo modo non viene mai memorizzata su disco ed è al sicuro da occhi indiscreti.
Naftuli Kay

55

La libreria di portachiavi python si integra con l' CryptProtectDataAPI su Windows (insieme alle relative API su Mac e Linux) che crittografa i dati con le credenziali di accesso dell'utente.

Utilizzo semplice:

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

Utilizzo se si desidera memorizzare il nome utente sul portachiavi:

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)

Più tardi per ottenere le tue informazioni dal portachiavi

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

Gli elementi vengono crittografati con le credenziali del sistema operativo dell'utente, quindi altre applicazioni in esecuzione nel tuo account utente potrebbero accedere alla password.

Per oscurare un po 'quella vulnerabilità è possibile crittografare / offuscare la password in qualche modo prima di memorizzarla nel portachiavi. Ovviamente, chiunque avesse preso di mira il tuo script sarebbe solo in grado di guardare la fonte e capire come decrittografare / annullare l'offuscamento della password, ma almeno impediresti ad alcune applicazioni di aspirare tutte le password nel vault e ottenere anche le tue .


Come deve essere archiviato il nome utente? Ha keyringil supporto recupero sia il nome utente e la password?
Stevoisiak

1
@DustinWyatt Uso intelligente di get_passwordper il nome utente. Tuttavia, penso che dovresti iniziare la risposta con l'esempio originale semplificato di keyring.set_password()ekeyring.get_password()
Stevoisiak,

keyringnon fa parte della libreria standard python
Ciasto piekarz

@Ciastopiekarz Qualcosa nella risposta ti ha portato a credere che facesse parte della libreria standard?
Dustin Wyatt

Elimina in keyringmodo sicuro la password dai registri e dalle post-password della memoria?
Kebman

26

Dopo aver esaminato le risposte a questa e alle domande correlate, ho messo insieme del codice utilizzando alcuni dei metodi suggeriti per crittografare e oscurare i dati segreti. Questo codice è specifico per quando lo script deve essere eseguito senza l'intervento dell'utente (se l'utente lo avvia manualmente, è meglio inserirli nella password e tenerla in memoria solo come suggerisce la risposta a questa domanda). Questo metodo non è super sicuro; Fondamentalmente, lo script può accedere alle informazioni segrete in modo che chiunque abbia accesso completo al sistema abbia lo script e i file associati e possa accedervi. Ciò che fa id nasconde i dati da un'ispezione casuale e lascia i file di dati stessi al sicuro se vengono esaminati individualmente o insieme senza lo script.

La mia motivazione per questo è un progetto che interroga alcuni dei miei conti bancari per monitorare le transazioni: ho bisogno che venga eseguito in background senza che io debba reinserire le password ogni minuto o due.

Basta incollare questo codice nella parte superiore dello script, modificare saltSeed e quindi utilizzare store () retrieve () e require () nel codice secondo necessità:

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 sicurezza di questo metodo sarebbe notevolmente migliorata se i permessi del sistema operativo fossero impostati sui file segreti per consentire solo allo script stesso di leggerli e se lo script stesso fosse compilato e contrassegnato solo come eseguibile (non leggibile). Alcuni di questi potrebbero essere automatizzati, ma non mi sono preoccupato. Probabilmente richiederebbe l'impostazione di un utente per lo script e l'esecuzione dello script come tale utente (e l'impostazione della proprietà dei file dello script su quell'utente).

Mi piacerebbe qualsiasi suggerimento, critica o altri punti di vulnerabilità a cui chiunque possa pensare. Sono abbastanza nuovo nello scrivere codice crittografico, quindi quello che ho fatto potrebbe quasi certamente essere migliorato.


26

Ci sono alcune opzioni per memorizzare le password e altri segreti che un programma Python deve usare, in particolare un programma che deve essere eseguito in background dove non può semplicemente chiedere all'utente di digitare la password.

Problemi da evitare:

  1. Controllo della password per il controllo del codice sorgente dove altri sviluppatori o anche il pubblico possono vederlo.
  2. Altri utenti sullo stesso server leggono la password da un file di configurazione o da un codice sorgente.
  3. Avere la password in un file sorgente in cui gli altri possono vederla sopra le tue spalle mentre la modifichi.

Opzione 1: SSH

Questa non è sempre un'opzione, ma probabilmente è la migliore. La tua chiave privata non viene mai trasmessa sulla rete, SSH esegue solo calcoli matematici per dimostrare che hai la chiave giusta.

Per farlo funzionare, hai bisogno di quanto segue:

  • Il database o qualsiasi cosa a cui stai accedendo deve essere accessibile da SSH. Prova a cercare "SSH" più qualsiasi servizio a cui stai accedendo. Ad esempio, "ssh postgresql" . Se questa non è una funzionalità del tuo database, passa all'opzione successiva.
  • Creare un account per eseguire il servizio che effettuerà chiamate al database e generare una chiave SSH .
  • Aggiungi la chiave pubblica al servizio che stai per chiamare o crea un account locale su quel server e installa lì la chiave pubblica.

Opzione 2: variabili d'ambiente

Questo è il più semplice, quindi potrebbe essere un buon punto di partenza. È descritto bene nell'app Twelve Factor . L'idea di base è che il codice sorgente estrae semplicemente la password o altri segreti dalle variabili di ambiente e quindi si configurano tali variabili di ambiente su ciascun sistema in cui si esegue il programma. Potrebbe anche essere un bel tocco se usi valori predefiniti che funzioneranno per la maggior parte degli sviluppatori. Devi bilanciarlo con il rendere il tuo software "sicuro per impostazione predefinita".

Ecco un esempio che estrae il server, il nome utente e la password dalle variabili di ambiente.

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)

Cerca come impostare le variabili di ambiente nel tuo sistema operativo e considera di eseguire il servizio con il proprio account. In questo modo non hai dati sensibili nelle variabili di ambiente quando esegui programmi nel tuo account. Quando si impostano queste variabili di ambiente, fare particolare attenzione che gli altri utenti non possano leggerle. Controlla i permessi dei file, ad esempio. Ovviamente tutti gli utenti con i permessi di root saranno in grado di leggerli, ma questo non può essere aiutato. Se stai usando systemd, guarda l' unità di servizio e fai attenzione a usarla al EnvironmentFileposto di Environmenteventuali segreti. Environmenti valori possono essere visualizzati da qualsiasi utente con systemctl show.

Opzione 3: file di configurazione

Questo è molto simile alle variabili d'ambiente, ma leggi i segreti da un file di testo. Trovo ancora le variabili di ambiente più flessibili per cose come strumenti di distribuzione e server di integrazione continua. Se decidi di utilizzare un file di configurazione, Python supporta diversi formati nella libreria standard, come JSON , INI , netrc e XML . Puoi anche trovare pacchetti esterni come PyYAML e TOML . Personalmente, trovo JSON e YAML i più semplici da usare e YAML consente i commenti.

Tre cose da considerare con i file di configurazione:

  1. Dov'è il file? Forse una posizione predefinita come ~/.my_appe un'opzione della riga di comando per utilizzare una posizione diversa.
  2. Assicurati che gli altri utenti non possano leggere il file.
  3. Ovviamente, non eseguire il commit del file di configurazione nel codice sorgente. Potresti voler eseguire il commit di un modello che gli utenti possono copiare nella loro directory home.

Opzione 4: modulo Python

Alcuni progetti mettono semplicemente i loro segreti in un modulo Python.

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

Quindi importa quel modulo per ottenere i valori.

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

db_connect(db_server, db_user, db_password)

Un progetto che utilizza questa tecnica è Django . Ovviamente, non dovresti impegnarti settings.pynel controllo del codice sorgente, anche se potresti voler eseguire il commit di un file chiamato settings_template.pyche gli utenti possono copiare e modificare.

Vedo alcuni problemi con questa tecnica:

  1. Gli sviluppatori potrebbero salvare accidentalmente il file nel controllo del codice sorgente. Aggiungerlo a .gitignoreriduce tale rischio.
  2. Parte del codice non è sotto il controllo del codice sorgente. Se sei disciplinato e metti solo stringhe e numeri qui, non sarà un problema. Se inizi a scrivere classi di filtri di registrazione qui, fermati!

Se il tuo progetto utilizza già questa tecnica, è facile passare alle variabili di ambiente. Basta spostare tutti i valori di impostazione nelle variabili di ambiente e modificare il modulo Python per leggere da quelle variabili di ambiente.


Ciao. Se il tuo progetto utilizza già questa tecnica, è facile passare alle variabili di ambiente. So come impostare manualmente le variabili di ambiente in Windows 10 ma posso accedervi dal mio codice Python usando os.getenv(). Come dovremmo farlo se il codice viene condiviso? Se il codice viene scaricato da un altro sviluppatore, come dovrebbe assicurarsi che le variabili d'ambiente siano già impostate per lui?
a_sid

Cerco di passare un valore predefinito ragionevole a os.getenv(), @a_sid, quindi il codice verrà eseguito almeno per un utente che non ha impostato le variabili di ambiente. Se non c'è un buon valore predefinito, solleva un chiaro errore quando ottieni None. Oltre a questo, inserisci commenti chiari nel file delle impostazioni. Se ho frainteso qualcosa, ti suggerisco di fare una domanda a parte.
Don Kirkby

8

Non ha molto senso cercare di crittografare la password: la persona a cui stai cercando di nasconderla ha lo script Python, che avrà il codice per decrittografarla. Il modo più veloce per ottenere la password sarà aggiungere un'istruzione print allo script Python appena prima che utilizzi la password con il servizio di terze parti.

Quindi memorizza la password come stringa nello script e codificala in base64 in modo che la sola lettura del file non sia sufficiente, quindi chiamala un giorno.


Dovrò modificare periodicamente il nome utente e la password e inserirò il tutto in un EXE per Windoze; Ho modificato il post per riflettere questo. Dovrei semplicemente basarlo64 ovunque finisca per conservarlo?
Naftuli Kay

Sono d'accordo che "cifrare" la password non aiuta, poiché la password in testo normale deve comunque essere ottenuta in modo automatizzato, e quindi deve essere ottenibile da qualsiasi cosa sia memorizzata. Ma ci sono approcci praticabili.
wberry

Pensavo di aver riconosciuto il tuo nome, eri nel pannello di principianti ed esperti su TalkPython, come principiante, il tuo messaggio ha davvero risuonato con me, grazie!
Manakin,

7

Penso che il meglio che puoi fare sia proteggere il file di script e il sistema su cui è in esecuzione.

Fondamentalmente fai quanto segue:

  • Usa i permessi del file system (chmod 400)
  • Password complessa per l'account del proprietario sul sistema
  • Ridurre la possibilità di compromettere il sistema (firewall, disabilitare i servizi non necessari, ecc.)
  • Rimuovi i privilegi di amministratore / root / sudo per coloro che non ne hanno bisogno

Sfortunatamente, è Windows, lo inserirò in un EXE e dovrò cambiare la password ogni tanto, quindi l'hard-coding non sarà un'opzione.
Naftuli Kay

1
Windows ha ancora le autorizzazioni del file system. Memorizza la password in un file esterno e rimuovi l'accesso di tutti, escluso il tuo. Probabilmente dovrai anche rimuovere i loro privilegi amministrativi.
Corey D

Sì, l'uso delle autorizzazioni è l'unica opzione di sicurezza affidabile qui. Ovviamente qualsiasi amministratore potrà comunque accedere ai dati (almeno su windows / le solite distribuzioni linux) ma poi è una battaglia già persa.
Voo

È vero. Quando la decrittografia della password è automatizzata, è altrettanto buono come avere una password in testo normale. La vera sicurezza sta nel bloccare l'account utente con accesso. La cosa migliore che si può fare è concedere autorizzazioni di sola lettura solo a quell'account utente. Eventualmente creare un utente speciale, specifico e solo per quel servizio.
Sepero

1

i sistemi operativi spesso supportano la protezione dei dati per l'utente. nel caso di Windows sembra che sia http://msdn.microsoft.com/en-us/library/aa380261.aspx

puoi chiamare le API win32 da python usando http://vermeulen.ca/python-win32api.html

per quanto ho capito, questo memorizzerà i dati in modo che sia possibile accedervi solo dall'account utilizzato per memorizzarli. se vuoi modificare i dati puoi farlo scrivendo il codice per estrarre, modificare e salvare il valore.


Questa mi sembra la scelta migliore, ma credo che questa risposta sia troppo incompleta per accettarla, dato che manca di esempi concreti.
ArtOfWarfare

1
Ci sono alcuni esempi per l'utilizzo di queste funzioni in Python qui: stackoverflow.com/questions/463832/using-dpapi-with-python
ArtOfWarfare

1

Ho usato la crittografia perché avevo problemi a installare (compilare) altre librerie comunemente menzionate sul mio 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)

Il mio script è in esecuzione in un sistema / stanza fisicamente sicuro. Cifro le credenziali con uno "script di crittografia" in un file di configurazione. E poi decifrarli quando ho bisogno di usarli. "Encrypter script" non è sul sistema reale, solo il file di configurazione crittografato lo è. Qualcuno che analizza il codice può facilmente rompere la crittografia analizzando il codice, ma puoi comunque compilarlo in un EXE se necessario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.