Ich muss einen Benutzernamen und ein Passwort sicher in Python speichern. Welche Optionen habe ich?


89

Ich schreibe ein kleines Python-Skript, das regelmäßig Informationen von einem Drittanbieter-Dienst mithilfe einer Kombination aus Benutzername und Kennwort abruft. Ich muss nichts erstellen, das zu 100% kugelsicher ist (gibt es überhaupt 100%?), Aber ich möchte ein gutes Maß an Sicherheit einbeziehen, sodass es zumindest lange dauern würde, bis jemand es kaputt macht.

Dieses Skript hat keine grafische cronBenutzeroberfläche und wird regelmäßig von ausgeführt. Daher funktioniert die Eingabe eines Kennworts bei jeder Ausführung zum Entschlüsseln nicht wirklich, und ich muss den Benutzernamen und das Kennwort entweder in einer verschlüsselten Datei oder verschlüsselt speichern in einer SQLite-Datenbank, was vorzuziehen wäre, da ich sowieso SQLite verwenden werde und möglicherweise das Kennwort irgendwann bearbeiten muss. Außerdem werde ich wahrscheinlich das gesamte Programm in eine EXE-Datei packen, da es derzeit ausschließlich für Windows ist.

Wie kann ich die Kombination aus Benutzername und Passwort sicher speichern, um sie regelmäßig über einen cronJob zu verwenden?


Antworten:


18

Ich empfehle eine ähnliche Strategie wie ssh-agent . Wenn Sie ssh-agent nicht direkt verwenden können, können Sie so etwas implementieren, sodass Ihr Kennwort nur im RAM gespeichert wird. Der Cron-Job könnte Anmeldeinformationen konfiguriert haben, um das tatsächliche Kennwort bei jeder delAusführung vom Agenten abzurufen, es einmal zu verwenden und es sofort mithilfe der Anweisung zu de-referenzieren .

Der Administrator muss noch das Kennwort eingeben, um ssh-agent beim Booten oder was auch immer zu starten. Dies ist jedoch ein vernünftiger Kompromiss, der verhindert, dass ein Klartextkennwort irgendwo auf der Festplatte gespeichert wird.


2
+1, das macht sehr viel Sinn. Ich könnte immer eine Benutzeroberfläche dafür erstellen, die den Benutzer beim Booten im Wesentlichen nach seinem Kennwort fragt. Auf diese Weise wird es nie auf der Festplatte gespeichert und ist vor neugierigen Blicken geschützt.
Naftuli Kay

51

Die Python-Schlüsselbundbibliothek ist in die CryptProtectDataAPI unter Windows (zusammen mit den relevanten APIs unter Mac und Linux) integriert, die Daten mit den Anmeldeinformationen des Benutzers verschlüsselt.

Einfache Verwendung:

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

Verwendung, wenn Sie den Benutzernamen auf dem Schlüsselbund speichern möchten:

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)

Später, um Ihre Informationen vom Schlüsselbund zu erhalten

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

Elemente werden mit den Anmeldeinformationen des Betriebssystems des Benutzers verschlüsselt, sodass andere Anwendungen, die in Ihrem Benutzerkonto ausgeführt werden, auf das Kennwort zugreifen können.

Um diese Sicherheitsanfälligkeit ein wenig zu verschleiern, können Sie das Kennwort auf irgendeine Weise verschlüsseln / verschleiern, bevor Sie es auf dem Schlüsselbund speichern. Natürlich kann jeder, der auf Ihr Skript abzielt, nur auf die Quelle schauen und herausfinden, wie das Kennwort entschlüsselt / entschlüsselt werden kann, aber Sie würden zumindest verhindern, dass eine Anwendung alle Kennwörter im Tresor aufsaugt und auch Ihre abruft .


Wie soll der Benutzername gespeichert werden? Hat das keyringAbrufen Unterstützung sowohl den Benutzernamen und das Passwort?
Stevoisiak

1
@DustinWyatt Clevere Verwendung get_passwordfür den Benutzernamen. Obwohl, ich denke, Sie sollten die Antwort mit dem ursprünglichen vereinfachten Beispiel von keyring.set_password()undkeyring.get_password()
Stevoisiak

keyringist nicht Teil der Python-Standardbibliothek
Ciasto piekarz

@Ciastopiekarz Hat Sie etwas an der Antwort zu der Annahme gebracht, dass sie Teil der Standardbibliothek ist?
Dustin Wyatt

24

Nachdem ich die Antworten auf diese und verwandte Fragen durchgesehen habe, habe ich einen Code zusammengestellt, der einige der vorgeschlagenen Methoden zum Verschlüsseln und Verschleiern geheimer Daten verwendet. Dieser Code ist speziell für den Fall gedacht, dass das Skript ohne Benutzereingriff ausgeführt werden muss (wenn der Benutzer es manuell startet, ist es am besten, ihn in das Kennwort einzugeben und es nur im Speicher zu behalten, wie die Antwort auf diese Frage nahelegt). Diese Methode ist nicht besonders sicher. Grundsätzlich kann das Skript auf die geheimen Informationen zugreifen, sodass jeder, der über vollständigen Systemzugriff verfügt, über das Skript und die zugehörigen Dateien verfügt und auf diese zugreifen kann. Was dies bewirkt, verdeckt die Daten bei gelegentlicher Überprüfung und lässt die Datendateien selbst sicher, wenn sie einzeln oder zusammen ohne das Skript geprüft werden.

Meine Motivation dafür ist ein Projekt, bei dem einige meiner Bankkonten abgefragt werden, um Transaktionen zu überwachen. Ich muss es im Hintergrund ausführen, ohne dass ich alle ein oder zwei Minuten Passwörter erneut eingebe.

Fügen Sie diesen Code einfach oben in Ihr Skript ein, ändern Sie den saltSeed und verwenden Sie dann store () retrieve () und require () in Ihrem Code nach Bedarf:

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

Die Sicherheit dieser Methode würde erheblich verbessert, wenn für die geheimen Dateien OS-Berechtigungen festgelegt würden, damit nur das Skript sie lesen kann, und wenn das Skript selbst kompiliert und als nur ausführbar (nicht lesbar) markiert würde. Einiges davon könnte automatisiert werden, aber ich habe mich nicht darum gekümmert. Es würde wahrscheinlich erfordern, einen Benutzer für das Skript einzurichten und das Skript als diesen Benutzer auszuführen (und dem Benutzer den Besitz der Skriptdateien festzulegen).

Ich würde mich über Vorschläge, Kritik oder andere Schwachstellen freuen, die sich jeder vorstellen kann. Ich bin ziemlich neu im Schreiben von Kryptocode, daher könnte das, was ich getan habe, mit ziemlicher Sicherheit verbessert werden.


20

Es gibt einige Optionen zum Speichern von Kennwörtern und anderen Geheimnissen, die ein Python-Programm verwenden muss, insbesondere ein Programm, das im Hintergrund ausgeführt werden muss und den Benutzer nicht einfach zur Eingabe des Kennworts auffordern kann.

Zu vermeidende Probleme:

  1. Einchecken des Passworts in die Quellcodeverwaltung, wo andere Entwickler oder sogar die Öffentlichkeit es sehen können.
  2. Andere Benutzer auf demselben Server lesen das Kennwort aus einer Konfigurationsdatei oder einem Quellcode.
  3. Das Passwort in einer Quelldatei haben, in der andere es über Ihre Schulter sehen können, während Sie es bearbeiten.

Option 1: SSH

Dies ist nicht immer eine Option, aber wahrscheinlich die beste. Ihr privater Schlüssel wird niemals über das Netzwerk übertragen. SSH führt lediglich mathematische Berechnungen durch, um zu beweisen, dass Sie den richtigen Schlüssel haben.

Damit es funktioniert, benötigen Sie Folgendes:

  • Auf die Datenbank oder auf was auch immer Sie zugreifen, muss SSH zugreifen können. Versuchen Sie, nach "SSH" und dem Dienst zu suchen, auf den Sie zugreifen. Zum Beispiel "ssh postgresql" . Wenn dies keine Funktion in Ihrer Datenbank ist, fahren Sie mit der nächsten Option fort.
  • Erstellen Sie ein Konto, um den Dienst auszuführen, der die Datenbank aufruft, und generieren Sie einen SSH-Schlüssel .
  • Fügen Sie entweder den öffentlichen Schlüssel zu dem Dienst hinzu, den Sie aufrufen möchten, oder erstellen Sie ein lokales Konto auf diesem Server und installieren Sie den öffentlichen Schlüssel dort.

Option 2: Umgebungsvariablen

Dieser ist der einfachste, also könnte es ein guter Anfang sein. Es ist gut in der Twelve Factor App beschrieben . Die Grundidee ist, dass Ihr Quellcode nur das Kennwort oder andere Geheimnisse von Umgebungsvariablen abruft und Sie diese Umgebungsvariablen dann auf jedem System konfigurieren, auf dem Sie das Programm ausführen. Es kann auch eine nette Geste sein, wenn Sie Standardwerte verwenden, die für die meisten Entwickler funktionieren. Sie müssen das abwägen, um Ihre Software "standardmäßig sicher" zu machen.

Hier ist ein Beispiel, in dem Server, Benutzername und Kennwort aus Umgebungsvariablen abgerufen werden.

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)

Suchen Sie nach Informationen zum Festlegen von Umgebungsvariablen in Ihrem Betriebssystem und erwägen Sie, den Dienst unter einem eigenen Konto auszuführen. Auf diese Weise haben Sie keine vertraulichen Daten in Umgebungsvariablen, wenn Sie Programme in Ihrem eigenen Konto ausführen. Achten Sie beim Einrichten dieser Umgebungsvariablen besonders darauf, dass andere Benutzer sie nicht lesen können. Überprüfen Sie beispielsweise die Dateiberechtigungen. Natürlich können alle Benutzer mit Root-Berechtigung sie lesen, aber das kann nicht geholfen werden.

Option 3: Konfigurationsdateien

Dies ist den Umgebungsvariablen sehr ähnlich, aber Sie lesen die Geheimnisse aus einer Textdatei. Ich finde die Umgebungsvariablen immer noch flexibler für Dinge wie Bereitstellungstools und Server für die kontinuierliche Integration. Wenn Sie sich für die Verwendung einer Konfigurationsdatei entscheiden, unterstützt Python verschiedene Formate in der Standardbibliothek, z. B. JSON , INI , netrc und XML . Sie können auch externe Pakete wie PyYAML und TOML finden . Ich persönlich finde JSON und YAML am einfachsten zu verwenden, und YAML erlaubt Kommentare.

Drei Dinge, die bei Konfigurationsdateien zu beachten sind:

  1. Wo ist die Datei? Möglicherweise ein Standardspeicherort wie ~/.my_appund eine Befehlszeilenoption, um einen anderen Speicherort zu verwenden.
  2. Stellen Sie sicher, dass andere Benutzer die Datei nicht lesen können.
  3. Übertragen Sie die Konfigurationsdatei natürlich nicht in den Quellcode. Möglicherweise möchten Sie eine Vorlage festschreiben, die Benutzer in ihr Ausgangsverzeichnis kopieren können.

Option 4: Python-Modul

Einige Projekte stecken ihre Geheimnisse einfach direkt in ein Python-Modul.

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

Importieren Sie dann dieses Modul, um die Werte abzurufen.

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

db_connect(db_server, db_user, db_password)

Ein Projekt, das diese Technik verwendet, ist Django . Natürlich sollten Sie sich nicht settings.pyauf die Quellcodeverwaltung festlegen, obwohl Sie möglicherweise eine Datei festlegen möchten, settings_template.pydie Benutzer kopieren und ändern können.

Ich sehe einige Probleme mit dieser Technik:

  1. Entwickler könnten die Datei versehentlich der Quellcodeverwaltung übergeben. Durch Hinzufügen .gitignorewird dieses Risiko verringert.
  2. Ein Teil Ihres Codes unterliegt nicht der Quellcodeverwaltung. Wenn Sie diszipliniert sind und hier nur Zeichenfolgen und Zahlen eingeben, ist dies kein Problem. Wenn Sie hier Protokollierungsfilterklassen schreiben, hören Sie auf!

Wenn Ihr Projekt diese Technik bereits verwendet, ist der Übergang zu Umgebungsvariablen einfach. Verschieben Sie einfach alle Einstellungswerte in Umgebungsvariablen und ändern Sie das Python-Modul so, dass es aus diesen Umgebungsvariablen liest.


8

Es macht nicht viel Sinn, das Passwort zu verschlüsseln: Die Person, vor der Sie es verstecken möchten, verfügt über das Python-Skript, das den Code zum Entschlüsseln enthält. Der schnellste Weg, um das Kennwort zu erhalten, besteht darin, dem Python-Skript eine Druckanweisung hinzuzufügen, bevor es das Kennwort mit dem Dienst eines Drittanbieters verwendet.

Speichern Sie das Kennwort also als Zeichenfolge im Skript, und codieren Sie es von base64 so, dass das Lesen der Datei nicht ausreicht. Rufen Sie es dann einen Tag lang auf.


Ich muss den Benutzernamen und das Passwort regelmäßig bearbeiten und das Ganze in eine EXE-Datei für Windoze einbinden. Ich habe den Beitrag bearbeitet, um dies widerzuspiegeln. Sollte ich es einfach base64, wo immer ich es am Ende speichere?
Naftuli Kay

Ich bin damit einverstanden, dass das "Verschlüsseln" des Passworts nicht hilft, da das Klartext-Passwort ohnehin automatisiert abgerufen werden muss und daher von allem, was gespeichert ist, erhältlich sein muss. Es gibt jedoch praktikable Ansätze.
Wberry

Obwohl ich Ihren Namen erkannt habe, waren Sie im Anfänger- und Expertengremium von TalkPython. Als Anfänger hat Ihre Nachricht wirklich Resonanz bei mir gefunden, danke!
Manakin

6

Ich denke, das Beste, was Sie tun können, ist, die Skriptdatei und das System, auf dem sie ausgeführt wird, zu schützen.

Grundsätzlich machen Sie Folgendes:

  • Dateisystemberechtigungen verwenden (chmod 400)
  • Starkes Passwort für das Konto des Besitzers im System
  • Reduzieren Sie die Gefahr, dass das System gefährdet wird (Firewall, Deaktivieren nicht benötigter Dienste usw.)
  • Entfernen Sie die Administrator- / Root- / Sudo-Berechtigungen für diejenigen, die sie nicht benötigen

Leider ist es Windows, ich werde es in eine EXE-Datei packen und ich muss das Passwort von Zeit zu Zeit ändern, so dass eine Hardcodierung keine Option ist.
Naftuli Kay

1
Windows verfügt weiterhin über Dateisystemberechtigungen. Speichern Sie das Passwort in einer externen Datei und entfernen Sie alle Zugriffsrechte außer Ihren eigenen. Sie müssen wahrscheinlich auch ihre Administratorrechte entfernen.
Corey D

Ja, die Verwendung von Berechtigungen ist hier die einzige zuverlässige Sicherheitsoption. Natürlich kann jeder Administrator weiterhin auf die Daten zugreifen (zumindest unter Windows / normalen Linux-Distributionen), aber das ist ein Kampf, der bereits verloren ist.
Voo

Es ist wahr. Wenn die Kennwortentschlüsselung automatisiert ist, ist dies genauso gut wie ein Nur-Text-Kennwort. Die eigentliche Sicherheit besteht darin, das Benutzerkonto mit Zugriff zu sperren. Das Beste, was Sie tun können, ist, nur diesem Benutzerkonto schreibgeschützte Berechtigungen zu erteilen. Erstellen Sie möglicherweise einen speziellen Benutzer, speziell und nur für diesen Dienst.
Sepero

1

Betriebssysteme unterstützen häufig das Sichern von Daten für den Benutzer. Bei Fenstern sieht es so aus http://msdn.microsoft.com/en-us/library/aa380261.aspx

Sie können win32 apis von Python mit aufrufen http://vermeulen.ca/python-win32api.html

Soweit ich weiß, werden die Daten so gespeichert, dass nur über das Konto, auf dem sie gespeichert wurden, auf sie zugegriffen werden kann. Wenn Sie die Daten bearbeiten möchten, können Sie dazu Code schreiben, um den Wert zu extrahieren, zu ändern und zu speichern.


Dies scheint mir die beste Wahl zu sein, aber ich denke, diese Antwort ist viel zu unvollständig, um sie zu akzeptieren, da es keine konkreten Beispiele gibt.
ArtOfWarfare

1
Es gibt einige Beispiele für die Verwendung dieser Funktionen in Python hier: stackoverflow.com/questions/463832/using-dpapi-with-python
ArtOfWarfare

0

Ich habe Cryptography verwendet, weil ich Probleme beim Installieren (Kompilieren) anderer häufig genannter Bibliotheken auf meinem System hatte. (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)

Mein Skript wird in einem physisch sicheren System / Raum ausgeführt. Ich verschlüssele Anmeldeinformationen mit einem "Verschlüsselungsskript" in eine Konfigurationsdatei. Und dann entschlüsseln, wenn ich sie verwenden muss. "Encrypter script" befindet sich nicht auf dem realen System, sondern nur in einer verschlüsselten Konfigurationsdatei. Jemand, der den Code analysiert, kann die Verschlüsselung leicht durch Analysieren des Codes aufheben, aber Sie können ihn bei Bedarf trotzdem in eine EXE-Datei kompilieren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.