Мне нужно надежно хранить имя пользователя и пароль в Python, каковы мои варианты?


Я пишу небольшой скрипт Python, который будет периодически извлекать информацию из стороннего сервиса, используя комбинацию имени пользователя и пароля. Мне не нужно создавать что-то на 100% пуленепробиваемое (существует ли вообще 100%?), но я хотел бы привлечь хорошую меру безопасности, чтобы, по крайней мере, это заняло бы много времени для кого-то, чтобы сломать его.

этот скрипт не будет иметь GUI и будет периодически запускаться cron, поэтому ввод пароля каждый раз, когда он запускается расшифровать вещи на самом деле не получится, и мне придется хранить имя пользователя и пароль либо в зашифрованном файле, либо в зашифрованной базе данных SQLite, что было бы предпочтительнее, поскольку я все равно буду использовать SQLite, и я может нужно изменить пароль в какой-то момент. Кроме того, я, вероятно, буду обертывать всю программу в EXE, так как на данный момент это исключительно для Windows.

Как я могу безопасно хранить имя пользователя и пароль комбо, которые будут использоваться периодически через a cron работу?

7 57

7 ответов:

Я рекомендую стратегию, подобную ssh-agent. Если вы не можете использовать ssh-агент напрямую, вы можете реализовать что-то подобное, так что ваш пароль хранится только в оперативной памяти. Задание cron могло настроить учетные данные для получения фактического пароля от агента при каждом запуске, использовать его один раз и немедленно удалить ссылку с помощью del заявление.

администратор должен ввести пароль для запуска SSH-агента, во время загрузки или что-то, но это разумный компромисс, который позволяет избежать простого текстового пароля, хранящегося в любом месте на диске.

The библиотека ключей python интегрируется с CryptProtectData API на Windows (наряду с соответствующими API на Mac и Linux), который шифрует данные с учетными данными пользователя для входа в систему.

простое использование:

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

использование если вы хотите сохранить имя пользователя на брелоке:

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)

позже, чтобы получить информацию из брелока

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

элементы шифруются с учетными данными операционной системы пользователя, таким образом, другие приложения, работающие в вашей учетной записи пользователя, смогут получить доступ к паролю.

чтобы немного скрыть эту уязвимость, вы можете зашифровать/запутать пароль каким-то образом, прежде чем хранить его в связке ключей. Конечно, любой, кто нацелился на ваш скрипт, просто сможет посмотреть на источник и выяснить, как расшифровать/не запутать пароль, но вы, по крайней мере, предотвратите, чтобы какое-то приложение пылесосило все пароли в хранилище и получало ваши.

после просмотра ответов на этот и связанные с ним вопросы, я собрал некоторый код, используя несколько предложенных методов шифрования и скрытия секретных данных. Этот код специально предназначен для того, когда скрипт должен запускаться без вмешательства пользователя (если пользователь запускает его вручную, лучше всего ввести пароль и сохранить его только в памяти, как следует из ответа на этот вопрос). Этот метод не является супер-безопасным; по сути, скрипт может получить доступ к секретной информации, поэтому любой, кто имеет полный доступ к системе, имеет сценарий и связанные с ним файлы и может получить к ним доступ. То, что это делает, id скрывает данные от случайной проверки и оставляет сами файлы данных в безопасности, если они рассматриваются по отдельности или вместе без сценария.

моей мотивацией для этого является проект, который опрашивает некоторые из моих банковских счетов для мониторинга транзакций - мне нужно, чтобы он работал в фоновом режиме без повторного ввода паролей каждую минуту или две.

просто вставьте этот код в верхней части скрипта, измените saltSeed, а затем используйте store () retrieve() и require () в своем коде по мере необходимости:

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

безопасность этого метода была бы значительно улучшена, если бы разрешения ОС были установлены на секретные файлы, чтобы позволить только самому сценарию читать их, и если сам сценарий был скомпилирован и помечен как исполняемый только (не читаемый). Некоторые из них могут быть автоматизированы, но я не беспокоился. Это, вероятно, потребует настройки пользователя для сценария и запуск сценария от имени этого пользователя (и установка права собственности на файлы сценария для этого пользователя).

Я хотел бы любые предложения, критические замечания или другие точки уязвимости, которые кто-либо может думать. Я довольно новичок в написании крипто кода, поэтому то, что я сделал, почти наверняка может быть улучшено.

Я думаю, лучшее, что вы можете сделать, это защитить файл и скрипт он работает.

в принципе сделать следующее:

  • использовать разрешения файловой системы (chmod 400)
  • надежный пароль для учетной записи владельца в системе
  • уменьшите возможность компрометации системы (брандмауэр, отключение ненужных служб и т. д.)
  • удалите права администратора/root/sudo для тех, кому это не нужно

нет особого смысла пытаться зашифровать пароль: у человека, от которого вы пытаетесь скрыть его, есть скрипт Python, который будет иметь код для его расшифровки. Самый быстрый способ получить пароль - добавить оператор печати в скрипт Python непосредственно перед использованием пароля с помощью стороннего сервиса.

поэтому сохраните пароль в виде строки в скрипте, и base64 закодирует его так, чтобы просто прочитать файл недостаточно, а затем назовите его днем.

операционные системы часто поддерживают защиту данных для пользователей. в случае windows это выглядит как http://msdn.microsoft.com/en-us/library/aa380261.aspx

вы можете вызвать win32 API из python с помощью http://vermeulen.ca/python-win32api.html

насколько я понимаю, это будет хранить данные, так что он может быть доступен только из учетной записи, используемой для его хранения. если вы хотите отредактировать данные, вы можете сделать это, написав код для извлечения, изменения и сохранения значения.

Я криптография потому что у меня были проблемы с установкой (компиляцией) других часто упоминаемых библиотек в моей системе. (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)

мой скрипт работает в физически безопасной системе / комнате. Я шифрую учетные данные с помощью "сценария шифрования"в файл конфигурации. А потом расшифровать, когда мне нужно их использовать. "Encrypter script" не находится в реальной системе, только зашифрованный файл конфигурации. Кто-то, кто анализирует код, может легко взломать шифрование анализ кода, но вы все равно можете скомпилировать его в EXE, если это необходимо.