Мне нужно безопасно хранить имя пользователя и пароль в Python, каковы мои варианты? - PullRequest
65 голосов
/ 10 августа 2011

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

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

Как можно безопасно хранить комбинацию имени пользователя и пароля для периодического использования с помощью задания cron?

Ответы [ 8 ]

31 голосов
/ 07 августа 2015

Библиотека python keyring интегрируется с API CryptProtectData в 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)  

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

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

23 голосов
/ 24 января 2013

Посмотрев ответы на этот и другие вопросы, я собрал код, используя несколько предложенных методов для шифрования и скрытия секретных данных.Этот код специально предназначен для случаев, когда сценарий должен выполняться без вмешательства пользователя (если пользователь запускает его вручную, лучше всего, чтобы он вставил пароль и оставил его в памяти только в соответствии с ответом на этот вопрос).Этот метод не является сверхбезопасным;По сути, скрипт может получить доступ к секретной информации, поэтому любой, кто имеет полный системный доступ, имеет скрипт и связанные с ним файлы и может получить к ним доступ.Что это делает, 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

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

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

15 голосов
/ 10 августа 2011

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

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

6 голосов
/ 10 августа 2011

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

В основном выполните следующие действия:

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

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

Проблемы, которых следует избегать:

  1. Проверка пароля в системе контроля версий, где его могут увидеть другие разработчики или даже публика.
  2. Другие пользователи на том же сервере читают пароль из файла конфигурации или исходного кода.
  3. Наличие пароля в исходном файле, где другие могут видеть его через плечо, пока вы редактируете его.

Вариант 1: SSH

Это не всегда вариант, но, вероятно, лучший. Ваш закрытый ключ никогда не передается по сети, SSH просто выполняет математические вычисления, чтобы доказать, что у вас есть правильный ключ.

Чтобы это работало, вам нужно следующее:

  • База данных или все, к чему вы обращаетесь, должно быть доступно по SSH. Попробуйте найти «SSH» плюс любой сервис, к которому вы обращаетесь. Например, "ssh postgresql" . Если это не функция в вашей базе данных, перейдите к следующей опции.
  • Создайте учетную запись для запуска службы, которая будет выполнять вызовы в базу данных, и создайте ключ SSH .
  • Либо добавьте открытый ключ к службе, которую вы собираетесь вызывать, либо создайте локальную учетную запись на этом сервере и установите открытый ключ там.

Вариант 2: переменные среды

Этот самый простой, так что это может быть хорошее место для начала. Это хорошо описано в приложении Twelve Factor . Основная идея заключается в том, что ваш исходный код просто извлекает пароль или другие секреты из переменных среды, а затем вы настраиваете эти переменные среды в каждой системе, в которой вы запускаете программу. Также может быть приятно, если вы используете значения по умолчанию, которые будут работать для большинства разработчиков. Вы должны сбалансировать это с тем, чтобы сделать ваше программное обеспечение «безопасным по умолчанию».

Вот пример, который извлекает сервер, имя пользователя и пароль из переменных среды.

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)

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

Вариант 3: файлы конфигурации

Это очень похоже на переменные среды, но вы читаете секреты из текстового файла. Я все еще нахожу переменные среды более гибкими для таких вещей, как инструменты развертывания и серверы непрерывной интеграции. Если вы решите использовать файл конфигурации, Python поддерживает несколько форматов в стандартной библиотеке, например JSON , INI , netrc и XML . Вы также можете найти внешние пакеты, такие как PyYAML и TOML . Лично я считаю JSON и YAML наиболее простым в использовании, а YAML позволяет комментировать.

Три вещи, которые следует учитывать при использовании файлов конфигурации:

  1. Где файл? Возможно расположение по умолчанию, например ~/.my_app, и параметр командной строки для использования другого расположения.
  2. Убедитесь, что другие пользователи не могут прочитать файл.
  3. Очевидно, что не передавайте файл конфигурации в исходный код. Возможно, вы захотите зафиксировать шаблон, который пользователи могут скопировать в свой домашний каталог.

Вариант 4: модуль Python

Некоторые проекты просто помещают свои секреты прямо в модуль Python.

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

Затем импортируйте этот модуль, чтобы получить значения.

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

db_connect(db_server, db_user, db_password)

Один проект, который использует эту технику - Django . Очевидно, что вы не должны фиксировать settings.py в управлении исходным кодом, хотя вам может потребоваться зафиксировать файл с именем settings_template.py, который пользователи могут копировать и изменять.

Я вижу несколько проблем с этой техникой:

  1. Разработчики могут случайно передать файл в систему контроля версий. Добавление его к .gitignore снижает этот риск.
  2. Часть вашего кода не находится под контролем исходного кода. Если вы дисциплинированы и ввели здесь только строки и цифры, это не будет проблемой. Если вы начнете писать здесь классы фильтров журналирования, остановитесь!

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

4 голосов
/ 10 августа 2011

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

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

1 голос
/ 10 августа 2011

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

Вы можете вызвать Win32 Apis из Python, используя http://vermeulen.ca/python-win32api.html

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

0 голосов
/ 27 января 2017

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...