Piszę mały skrypt w Pythonie, który będzie okresowo pobierał informacje z usługi innej firmy, używając kombinacji nazwy użytkownika i hasła. Nie muszę tworzyć czegoś, co jest w 100% kuloodporne (czy 100% w ogóle istnieje?), Ale chciałbym zapewnić dobre zabezpieczenie, więc przynajmniej zajęłoby to dużo czasu, zanim ktoś to złamie.
Ten skrypt nie będzie miał GUI i będzie uruchamiany okresowo przez cron
, więc wprowadzanie hasła za każdym razem, gdy jest uruchamiany w celu odszyfrowania rzeczy, tak naprawdę nie zadziała i będę musiał przechowywać nazwę użytkownika i hasło w zaszyfrowanym pliku lub zaszyfrowanym w bazie danych SQLite, co byłoby lepsze, ponieważ i tak będę używać SQLite, a może być konieczne zmodyfikowanie hasła w pewnym momencie. Ponadto prawdopodobnie zapakuję cały program w plik EXE, ponieważ w tym momencie jest on przeznaczony wyłącznie dla systemu Windows.
Jak mogę bezpiecznie przechowywać kombinację nazwy użytkownika i hasła, które będą używane okresowo w ramach cron
zadania?
źródło
Odpowiedzi:
Polecam strategię podobną do ssh-agent . Jeśli nie możesz bezpośrednio używać ssh-agent, możesz zaimplementować coś takiego, aby twoje hasło było przechowywane tylko w pamięci RAM. Zadanie cron mogło skonfigurować poświadczenia, aby uzyskać rzeczywiste hasło od agenta przy każdym uruchomieniu, użyć go raz i natychmiast usunąć odwołanie za pomocą
del
instrukcji.Administrator nadal musi wprowadzić hasło, aby uruchomić ssh-agent, przy starcie lub czymkolwiek, ale jest to rozsądny kompromis, który pozwala uniknąć przechowywania hasła w postaci zwykłego tekstu w dowolnym miejscu na dysku.
źródło
Pyton brelok biblioteki integruje się z
CryptProtectData
API w systemie Windows (wraz z odpowiednimi API na Mac i Linux), który szyfruje dane z poświadczeniami logowania użytkownika.Proste użycie:
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
Sposób użycia, jeśli chcesz przechowywać nazwę użytkownika na breloku:
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)
Później, aby uzyskać informacje z breloczka
# 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)
Elementy są szyfrowane przy użyciu poświadczeń systemu operacyjnego użytkownika, dzięki czemu inne aplikacje działające na koncie użytkownika będą miały dostęp do hasła.
Aby nieco ukryć tę lukę, możesz zaszyfrować / zaciemnić hasło w jakiś sposób przed zapisaniem go w bazie kluczy. Oczywiście każdy, kto celował w twój skrypt, mógłby po prostu spojrzeć na źródło i dowiedzieć się, jak odszyfrować / odszyfrować hasło, ale przynajmniej zapobiegniesz, aby niektóre aplikacje odkurzyły wszystkie hasła w skarbcu i również twoje .
źródło
keyring
obsługuje odzyskiwanie zarówno nazwy użytkownika, jak i hasła?get_password
jako nazwy użytkownika. Chociaż myślę, że powinieneś rozpocząć odpowiedź od oryginalnego uproszczonego przykładukeyring.set_password()
ikeyring.get_password()
keyring
nie jest częścią standardowej biblioteki Pythonakeyring
bezpiecznie usuwa hasło z dzienników i pamięci po słowach?Po przejrzeniu odpowiedzi na to i pokrewne pytania stworzyłem kod, korzystając z kilku sugerowanych metod szyfrowania i ukrywania tajnych danych. Ten kod jest szczególnie przydatny, gdy skrypt ma działać bez interwencji użytkownika (jeśli użytkownik uruchamia go ręcznie, najlepiej jest wprowadzić hasło i zachować je w pamięci tylko zgodnie z odpowiedzią na to pytanie). Ta metoda nie jest super bezpieczna; zasadniczo skrypt może uzyskać dostęp do tajnych informacji, więc każdy, kto ma pełny dostęp do systemu, ma skrypt i powiązane z nim pliki oraz może uzyskać do nich dostęp. To, co robi, id zasłania dane przed przypadkową inspekcją i pozostawia same pliki danych, jeśli są one badane pojedynczo lub razem bez skryptu.
Moją motywacją do tego jest projekt, który sonduje niektóre z moich rachunków bankowych w celu monitorowania transakcji - potrzebuję, aby działał w tle bez ponownego wprowadzania haseł co minutę lub dwie.
Po prostu wklej ten kod na górze skryptu, zmień saltSeed, a następnie użyj store () retrieve () i require () w swoim kodzie w razie potrzeby:
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
Bezpieczeństwo tej metody zostałoby znacznie poprawione, gdyby uprawnienia systemu operacyjnego zostały ustawione na tajnych plikach, aby umożliwić tylko samemu skryptowi odczytanie ich, i gdyby sam skrypt został skompilowany i oznaczony jako tylko wykonywalny (nieczytelny). Niektóre z nich można by zautomatyzować, ale nie przejmowałem się. Prawdopodobnie wymagałoby to skonfigurowania użytkownika dla skryptu i uruchomienia skryptu jako ten użytkownik (i przypisania prawa własności do plików skryptu temu użytkownikowi).
Z przyjemnością przyjmuję wszelkie sugestie, krytykę lub inne słabe punkty, o których każdy może pomyśleć. Jestem całkiem nowy w pisaniu kodu kryptograficznego, więc to, co zrobiłem, prawie na pewno można ulepszyć.
źródło
Istnieje kilka opcji przechowywania haseł i innych sekretów, których program w Pythonie musi używać, w szczególności program, który musi działać w tle, gdzie nie może po prostu poprosić użytkownika o wpisanie hasła.
Problemy, których należy unikać:
Opcja 1: SSH
Nie zawsze jest to opcja, ale prawdopodobnie jest najlepsza. Twój klucz prywatny nigdy nie jest przesyłany przez sieć, SSH wykonuje tylko obliczenia matematyczne, aby udowodnić, że masz właściwy klucz.
Aby to działało, potrzebujesz:
Opcja 2: zmienne środowiskowe
Ten jest najprostszy, więc może być dobrym miejscem do rozpoczęcia. Jest to dobrze opisane w aplikacji Twelve Factor . Podstawowym pomysłem jest to, że kod źródłowy po prostu pobiera hasło lub inne wpisy tajne ze zmiennych środowiskowych, a następnie konfiguruje te zmienne środowiskowe w każdym systemie, w którym uruchamiasz program. Może być również miłym akcentem, jeśli użyjesz wartości domyślnych, które będą działać dla większości programistów. Musisz zrównoważyć to z zapewnieniem „domyślnego bezpieczeństwa” oprogramowania.
Oto przykład, który pobiera serwer, nazwę użytkownika i hasło ze zmiennych środowiskowych.
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)
Sprawdź, jak ustawić zmienne środowiskowe w systemie operacyjnym i rozważ uruchomienie usługi na własnym koncie. W ten sposób nie masz wrażliwych danych w zmiennych środowiskowych, gdy uruchamiasz programy na swoim własnym koncie. Po skonfigurowaniu tych zmiennych środowiskowych należy uważać, aby inni użytkownicy nie mogli ich odczytać. Sprawdź na przykład uprawnienia do plików. Oczywiście każdy użytkownik z uprawnieniami roota będzie mógł je przeczytać, ale nie można na to poradzić. Jeśli używasz systemd, spójrz na jednostkę usługową i uważaj, aby użyć
EnvironmentFile
zamiastEnvironment
jakichkolwiek sekretów.Environment
wartości mogą być przeglądane przez każdego użytkownika zsystemctl show
.Opcja 3: Pliki konfiguracyjne
Jest to bardzo podobne do zmiennych środowiskowych, ale odczytujesz sekrety z pliku tekstowego. Nadal uważam, że zmienne środowiskowe są bardziej elastyczne w przypadku narzędzi do wdrażania i serwerów ciągłej integracji. Jeśli zdecydujesz się użyć pliku konfiguracyjnego, Python obsługuje kilka formatów w bibliotece standardowej, takich jak JSON , INI , netrc i XML . Możesz również znaleźć pakiety zewnętrzne, takie jak PyYAML i TOML . Osobiście uważam, że JSON i YAML są najprostsze w użyciu, a YAML pozwala na komentarze.
Trzy rzeczy do rozważenia w przypadku plików konfiguracyjnych:
~/.my_app
i opcja wiersza poleceń, aby użyć innej lokalizacji.Opcja 4: moduł Python
Niektóre projekty po prostu umieszczają swoje sekrety bezpośrednio w module Pythona.
# settings.py db_server = 'dbhost1' db_user = 'my_app' db_password = 'correcthorsebatterystaple'
Następnie zaimportuj ten moduł, aby uzyskać wartości.
# my_app.py from settings import db_server, db_user, db_password db_connect(db_server, db_user, db_password)
Jednym z projektów wykorzystujących tę technikę jest Django . Oczywiście nie powinieneś angażować
settings.py
się w kontrolę źródła, chociaż możesz chcieć zatwierdzić plik o nazwie,settings_template.py
który użytkownicy mogą kopiować i modyfikować.Widzę kilka problemów z tą techniką:
.gitignore
zmniejsza to ryzyko.Jeśli Twój projekt już korzysta z tej techniki, łatwo jest przejść do zmiennych środowiskowych. Po prostu przenieś wszystkie wartości ustawień do zmiennych środowiskowych i zmień moduł Pythona, aby czytał z tych zmiennych środowiskowych.
źródło
os.getenv()
. Jak powinniśmy to zrobić, jeśli kod jest udostępniany? Jeśli kod jest pobierany przez innego programistę, w jaki sposób powinien upewnić się, że zmienne środowiskowe są już dla niego ustawione?os.getenv()
@a_sid, aby kod działał przynajmniej dla użytkownika, który nie ustawił zmiennych środowiskowych. Jeśli nie ma dobrej wartości domyślnej, zgłoś wyraźny błąd, gdy otrzymaszNone
. Poza tym umieść wyraźne komentarze w pliku ustawień. Jeśli coś źle zrozumiałem, proponuję zadać osobne pytanie.Nie ma sensu próbować zaszyfrować hasła: osoba, przed którą próbujesz je ukryć, ma skrypt Pythona, który będzie miał kod do odszyfrowania. Najszybszym sposobem uzyskania hasła będzie dodanie instrukcji print do skryptu Pythona tuż przed użyciem hasła w usłudze innej firmy.
Więc zapisz hasło jako ciąg znaków w skrypcie i zakoduj base64, aby samo odczytanie pliku nie wystarczyło, a następnie nazwij to dzień.
źródło
Myślę, że najlepsze, co możesz zrobić, to chronić plik skryptu i system, na którym działa.
Zasadniczo wykonaj następujące czynności:
źródło
systemy operacyjne często obsługują zabezpieczenie danych użytkownika. w przypadku Windows wygląda to jak http://msdn.microsoft.com/en-us/library/aa380261.aspx
możesz wywołać apis win32 z Pythona za pomocą http://vermeulen.ca/python-win32api.html
o ile rozumiem, dane będą przechowywane tak, aby można było uzyskać do nich dostęp tylko z konta używanego do ich przechowywania. jeśli chcesz edytować dane, możesz to zrobić, pisząc kod, aby wyodrębnić, zmienić i zapisać wartość.
źródło
Użyłem kryptografii, ponieważ miałem problemy z instalacją (kompilacją) innych powszechnie wymienianych bibliotek w moim systemie. (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)
Mój skrypt działa w fizycznie bezpiecznym systemie / pomieszczeniu. Szyfruję dane uwierzytelniające „skryptem szyfrującym” do pliku konfiguracyjnego. A potem odszyfruj, kiedy muszę ich użyć. "Skryptu szyfrującego" nie ma w rzeczywistym systemie, jest tylko zaszyfrowany plik konfiguracyjny. Ktoś, kto analizuje kod, może łatwo złamać szyfrowanie, analizując kod, ale nadal możesz skompilować go do pliku EXE, jeśli to konieczne.
źródło