Muszę bezpiecznie przechowywać nazwę użytkownika i hasło w Pythonie, jakie są moje opcje?

Piszę mały skrypt Pythona, który będzie okresowo pobierał informacje z usługi 3rd party za pomocą 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 zaangażować w to dobry środek bezpieczeństwa, więc przynajmniej długo by to trwało, zanim ktoś go złamie.

Ten skrypt nie będzie miał GUI i będzie uruchamiany okresowo przez cron, więc wpisywanie hasła za każdym razem, gdy jest uruchamiany, Aby odszyfrować rzeczy tak naprawdę nie będzie dział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ż będę używać SQLite i tak, i Może trzeba edytować hasło w pewnym momencie. Ponadto, prawdopodobnie będę owijać cały program w EXE, ponieważ jest to wyłącznie dla Windows w tym momencie.

Jak mogę bezpiecznie przechowywać kombinację nazwy użytkownika i hasła, które mają być okresowo używane w zadaniu cron?

Author: Naftuli Kay, 2011-08-10

7 answers

Polecam strategię podobną do ssh-agent . Jeśli nie możesz użyć ssh-agent bezpośrednio można zaimplementować coś takiego, tak, że hasło jest przechowywane tylko w pamięci RAM. Zadanie cron mogło mieć skonfigurowane poświadczenia, aby pobierać rzeczywiste hasło od agenta za każdym razem, gdy jest uruchamiane, używać go raz i natychmiast odwoływać się do niego za pomocą instrukcji del.

Administrator musi jeszcze wprowadzić hasło, aby uruchomić ssh-agent, w czasie rozruchu lub cokolwiek innego, ale jest to rozsądne kompromis, który pozwala uniknąć zapisywania hasła w dowolnym miejscu na dysku.

 14
Author: wberry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-10 18:03:51

Biblioteka Python keyring integruje się z CryptProtectData API w systemie Windows (wraz z odpowiednimi API w systemach Mac i Linux), które szyfruje dane za pomocą poświadczeń 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

Użycie jeśli chcesz zapisać 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 breloka

# 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 za pomocą poświadczeń systemu operacyjnego użytkownika, dzięki czemu inne aplikacje uruchomione na twoim koncie użytkownika będą dostęp do hasła.

Aby zasłonić tę lukę, możesz w jakiś sposób zaszyfrować/zaciemnić hasło przed zapisaniem go na breloku. Oczywiście każdy, kto celował w twój skrypt, byłby w stanie spojrzeć na źródło i dowiedzieć się, jak odszyfrować/odszyfrować hasło, ale przynajmniej zapobiegniesz odkurzaniu wszystkich haseł w skarbcu przez jakąś aplikację i uzyskaniu twojego.

 23
Author: Dustin Wyatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-16 16:19:18

Po zapoznaniu się z odpowiedziami na to i powiązane pytania, ułożyłem trochę kodu przy użyciu kilku sugerowanych metod szyfrowania i zasłaniania tajnych danych. Ten kod jest specjalnie przeznaczony do sytuacji, gdy skrypt musi działać bez interwencji użytkownika(jeśli użytkownik uruchamia go ręcznie, najlepiej jest umieścić je w haśle i zachować je tylko w pamięci, jak sugeruje odpowiedź na to pytanie). Ta metoda nie jest super-bezpieczne; 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 i może uzyskać do nich dostęp. To, co robi id, zasłania dane przed przypadkową inspekcją i pozostawia same pliki danych bezpieczne, jeśli są badane pojedynczo lub razem bez skryptu.

Moją motywacją do tego jest projekt, który sprawdza niektóre moje konta bankowe w celu monitorowania transakcji - potrzebuję go, aby działał w tle bez ponownego wprowadzania haseł co minutę lub dwie.

Po prostu wklej ten kod w górnej części skryptu Zmień saltSeed, a następnie użyj store() retrieve() I require () w 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 były ustawione na tajnych plikach tak, aby skrypt mógł je odczytać, a sam skrypt został skompilowany i oznaczony jako wykonywalny (nie do odczytania). Część z nich może być zautomatyzowana, ale nie zawracałem sobie głowy. Prawdopodobnie wymagałoby to skonfigurowania użytkownika dla skryptu i uruchomienie skryptu jako tego użytkownika (i ustawienie własności plików skryptu na tego użytkownika).

Chciałbym, aby wszelkie sugestie, krytyka lub inne punkty wrażliwości, które każdy może wymyślić. Jestem całkiem nowy w pisaniu kodu kryptograficznego, więc to, co zrobiłem, prawie na pewno można poprawić.
 22
Author: drodgers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-24 04:53:10

Myślę, że najlepiej jest chronić plik skryptu i system, na którym jest uruchomiony.

Zasadniczo wykonaj następujące czynności:

  • Użyj uprawnień systemu plików (chmod 400)
  • silne hasło do konta właściciela w systemie
  • zmniejsz możliwość naruszenia systemu (zapora sieciowa, wyłączenie niepotrzebnych usług itp.)
  • Usuń uprawnienia administratora/roota / sudo dla tych, którzy tego nie potrzebują
 5
Author: Corey D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-10 17:24:26

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 jego 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 zachowaj hasło jako ciąg znaków w skrypcie i base64 Zakoduj je tak, aby samo odczytanie pliku nie wystarczyło, a następnie nazwij to dniem.

 3
Author: Ned Batchelder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-10 17:18:21

Systemy operacyjne często mają wsparcie dla zabezpieczenia danych dla użytkownika. w przypadku windows wygląda na to, że to http://msdn.microsoft.com/en-us/library/aa380261.aspx

Możesz wywołać API win32 z Pythona używając http://vermeulen.ca/python-win32api.html

O ile rozumiem, będzie to przechowywać dane tak, że można 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 do wyodrębnienia, zmiany i zapisz wartość.

 1
Author: andrew cooke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-10 17:38:57

Użyłem kryptografii ponieważ miałem problemy z instalacją (kompilacją) innych powszechnie wymienionych 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 zabezpieczonym systemie / pokoju. Szyfruję poświadczenia za pomocą "skryptu szyfrującego" do pliku konfiguracyjnego. A potem odszyfruj, kiedy będę musiał ich użyć. "Encrypter script" nie jest w prawdziwym systemie, tylko zaszyfrowany plik konfiguracyjny jest. Ktoś, kto analizuje kod, może łatwo złamać szyfrowanie, analizując kod, ale w razie potrzeby możesz go skompilować do EXE.

 1
Author: KRBA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-27 07:49:52