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


95

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

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

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


Ответы:


19

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

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


2
+1, в этом есть большой смысл. Я всегда мог создать для него пользовательский интерфейс, который по существу запрашивает у пользователя его пароль при загрузке, таким образом он никогда не сохраняется на диске и безопасен от посторонних глаз.
Нафтули Кей

55

Библиотека связки ключей python интегрируется с CryptProtectDataAPI в 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)  

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

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


Как следует хранить имя пользователя? keyringПоддерживает ли получение как имени пользователя, так и пароля?
Stevoisiak

1
@DustinWyatt Умное использование get_passwordимени пользователя. Хотя, я думаю, вам следует начать ответ с оригинального упрощенного примера keyring.set_password()иkeyring.get_password()
Stevoisiak

keyringне является частью стандартной библиотеки Python
Ciasto piekarz

@Ciastopiekarz Что-то в ответе заставило вас поверить, что он был частью стандартной библиотеки?
Дастин Вятт,

Можно ли keyringбезопасно удалить пароль из журналов и послесловий в памяти?
Кебман

26

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

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

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


26

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

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

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

Вариант 1: SSH

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

Для того, чтобы он заработал, вам понадобится следующее:

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

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

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

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

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 смогут читать их, но тут ничего не поделаешь. Если вы используете systemd, посмотрите на служебную единицу и будьте осторожны при использовании EnvironmentFileвместо Environmentлюбых секретов. Environmentзначения могут быть просмотрены любым пользователем с systemctl show.

Вариант 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 на чтение из этих переменных среды.


Здравствуйте. Если ваш проект уже использует эту технику, легко перейти на переменные среды. Я знаю, как устанавливать переменные среды в Windows 10 вручную, но могу получить к ним доступ из моего кода Python, используя os.getenv(). Как нам это сделать, если код распространяется? Если код загружен другим разработчиком, как он / она должен убедиться, что переменные среды уже установлены для него?
a_sid

Я пытаюсь передать разумное значение по умолчанию для os.getenv()@a_sid, чтобы код работал, по крайней мере, для пользователя, который не установил переменные среды. Если подходящего значения по умолчанию нет, при получении выдайте явную ошибку None. Кроме того, оставьте четкие комментарии в файле настроек. Если я что-то неправильно понял, предлагаю вам задать отдельный вопрос.
Дон Киркби,

8

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

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


Мне нужно будет периодически редактировать имя пользователя и пароль, и я буду упаковывать все это в EXE для Windoze; Я отредактировал сообщение, чтобы отразить это. Должен ли я просто использовать base64, где бы я его ни хранил?
Нафтули Кей

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

Думал, что я узнал ваше имя, вы были на панели для начинающих и экспертов на TalkPython, ваше сообщение действительно нашло отклик у меня, спасибо!
Манакин

7

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

В основном делайте следующее:

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

К сожалению, это Windows, я буду упаковывать ее в EXE, и мне придется время от времени менять пароль, поэтому жесткое кодирование не будет вариантом.
Нафтули Кей

1
Windows по-прежнему имеет разрешения файловой системы. Сохраните пароль во внешнем файле и удалите доступ всем, кроме вашего собственного. Вероятно, вам также придется удалить их административные привилегии.
Corey D

Да, использование разрешений - единственный надежный вариант безопасности. Очевидно, что любой администратор по-прежнему сможет получить доступ к данным (по крайней мере, в Windows / обычных дистрибутивах Linux), но тогда битва уже проиграна.
Voo

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

1

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

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

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


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

1
Вот несколько примеров использования этих функций в Python: stackoverflow.com/questions/463832/using-dpapi-with-python
ArtOfWarfare

1

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.