Мне нужно безопасно хранить имя пользователя и пароль в Python, каковы мои варианты?
Я пишу небольшой Python script, который будет периодически извлекать информацию из сторонней службы, используя комбинацию имени пользователя и пароля. Мне не нужно создавать что-то, что на 100% пуленепробиваемо (на 100% даже существует?), Но я хотел бы привлечь хорошую меру безопасности, так что, по крайней мере, потребуется, чтобы кто-то сломал ее.
Этот script не будет иметь графический интерфейс и будет периодически запускаться с помощью cron
, поэтому ввод пароля каждый раз, когда он запускается для дешифрования, не будет работать, и мне нужно будет сохранить имя пользователя и пароль в зашифрованном файле или зашифрованном в базе данных SQLite, что было бы предпочтительнее, поскольку я буду использовать SQLite в любом случае, и мне может понадобиться изменить пароль в какой-то момент. Кроме того, я, вероятно, буду обертывать всю программу в EXE, как это происходит исключительно для Windows на этом этапе.
Как я могу безопасно хранить комбинацию имени пользователя и пароля для периодического использования с помощью задания cron
?
Ответы
Ответ 1
Я рекомендую стратегию, похожую на ssh-agent. Если вы не можете использовать ssh-agent напрямую, вы можете реализовать что-то вроде этого, чтобы ваш пароль хранился только в ОЗУ. Задача cron могла бы настроить учетные данные для получения фактического пароля от агента каждый раз, когда он запускается, использовать его один раз и немедленно удалить его с помощью инструкции del
.
Администратор по-прежнему должен ввести пароль для запуска ssh-agent, во время загрузки или что-то еще, но это разумный компромисс, который позволяет избежать использования обычного текстового пароля, хранящегося в любом месте на диске.
Ответ 2
Библиотека ключей 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)
Элементы зашифровываются с учетными данными операционной системы пользователя, поэтому другие приложения, запущенные в вашей учетной записи пользователя, смогут получить доступ к паролю.
Чтобы немного скрыть эту уязвимость, вы могли каким-то образом зашифровать/обфускать пароль, прежде чем хранить его в брелоке. Конечно, любой, кто нацелился на ваш скрипт, просто сможет посмотреть на источник и выяснить, как дешифровать/разузнавать пароль, но вы, по крайней мере, должны были бы не допустить, чтобы какое-то приложение очищало все пароли в хранилище и получало ваши,
Ответ 3
Посмотрев хотя бы ответы на этот и связанные вопросы, я собрал некоторый код, используя несколько предложенных методов шифрования и скрытия секретных данных. Этот код предназначен для тех случаев, когда script должен запускаться без вмешательства пользователя (если пользователь запускает его вручную, лучше всего, чтобы он ввел пароль и сохранил его только в памяти, как предполагает ответ на этот вопрос). Этот метод не является сверхбезопасным; в принципе, script может получить доступ к секретной информации, поэтому любой, у кого есть полный системный доступ, имеет script и связанные с ним файлы и может получить к ним доступ. Что это делает, id скрывает данные от случайного осмотра и оставляет файлы данных безопасными, если они рассматриваются отдельно или вместе без script.
Моей мотивацией для этого является проект, который опроса некоторых моих банковских счетов для мониторинга транзакций - мне нужно, чтобы он работал в фоновом режиме, не заново меняя пароли каждую минуту или две.
Просто вставьте этот код вверху вашего script, измените 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 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, чтобы позволить самому script читать их, а сам script был скомпилирован и помечен как только исполняемый файл (не читаемый). Некоторые из них могут быть автоматизированы, но я не беспокоил. Для этого, вероятно, потребуется настроить пользователя для script и запустить script в качестве этого пользователя (и установить принадлежность файлам script этому пользователю).
Мне нравятся любые предложения, критика или другие уязвимые места, о которых каждый может подумать. Я новичок в написании криптокода, поэтому то, что я сделал, почти наверняка может быть улучшено.
Ответ 4
Я думаю, что лучше всего вы можете защитить файл script и систему, в которой он работает.
В основном выполните следующие действия:
- Использовать разрешения файловой системы (chmod 400)
- Сильный пароль для учетной записи владельца в системе
- Уменьшить вероятность взлома системы (брандмауэр, отключить ненужные службы и т.д.)
- Удалить привилегии администратора /root/sudo для тех, кому это не нужно
Ответ 5
Нет смысла пытаться зашифровать пароль: человек, с которого вы его скрываете, имеет Python script, который будет иметь код для его расшифровки. Самый быстрый способ получить пароль будет заключаться в том, чтобы добавить оператор печати в Python script непосредственно перед использованием пароля с помощью сторонней службы.
Итак, сохраните пароль как строку в script, а base64 закодируйте его так, чтобы просто прочитать файл недостаточно, а затем называть его днем.
Ответ 6
Существует несколько вариантов хранения паролей и других секретов, которые должна использовать программа Python, особенно программа, которая должна работать в фоновом режиме, где она не может просто попросить пользователя ввести пароль.
Проблемы, которых следует избегать:
- Проверка пароля в системе контроля версий, где его могут увидеть другие разработчики или даже публика.
- Другие пользователи на том же сервере читают пароль из файла конфигурации или исходного кода.
- Наличие пароля в исходном файле, где другие могут видеть его через плечо, пока вы редактируете его.
Вариант 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 сможет их прочитать, но с этим ничего не поделаешь.
Вариант 3: файлы конфигурации
Это очень похоже на переменные среды, но вы читаете секреты из текстового файла. Я все еще нахожу переменные среды более гибкими для таких вещей, как инструменты развертывания и серверы непрерывной интеграции. Если вы решите использовать файл конфигурации, Python поддерживает несколько форматов в стандартной библиотеке, таких как JSON, INI, netrc и XML. Вы также можете найти внешние пакеты, такие как PyYAML и TOML. Лично я считаю JSON и YAML наиболее простым в использовании, а YAML позволяет комментировать.
Три вещи, чтобы рассмотреть с файлами конфигурации:
- Где файл? Возможно расположение по умолчанию, например
~/.my_app
, и параметр командной строки для использования другого расположения. - Убедитесь, что другие пользователи не могут прочитать файл.
- Очевидно, не фиксируйте файл конфигурации в исходном коде. Возможно, вы захотите зафиксировать шаблон, который пользователи могут скопировать в свой домашний каталог.
Вариант 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
который пользователи могут копировать и изменять.
Я вижу несколько проблем с этой техникой:
- Разработчики могут случайно передать файл в систему контроля версий. Добавление его в
.gitignore
снижает этот риск. - Часть вашего кода не находится под контролем исходного кода. Если вы дисциплинированы и ввели здесь только строки и цифры, это не будет проблемой. Если вы начнете писать здесь классы фильтров журналирования, остановитесь!
Если ваш проект уже использует эту технику, легко перейти к переменным среды. Просто переместите все значения настроек в переменные среды и измените модуль Python для чтения из этих переменных среды.
Ответ 7
Операционные системы часто поддерживают поддержку данных для пользователя. в случае окон это выглядит как http://msdn.microsoft.com/en-us/library/aa380261.aspx
вы можете вызвать win32 apis из python, используя http://vermeulen.ca/python-win32api.html
насколько я понимаю, это сохранит данные, чтобы к ним можно было получить доступ только из учетной записи, используемой для его хранения. если вы хотите отредактировать данные, вы можете сделать это, написав код для извлечения, изменения и сохранения значения.
Ответ 8
Я использовал Cryptography, потому что у меня были проблемы с установкой (компиляцией) других часто упоминаемых библиотек в моей системе, (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)
My script работает в физически защищенной системе/комнате. Я шифрую учетные данные с помощью "encrypter script" в файле конфигурации. И затем расшифруйте, когда мне нужно их использовать.
"Encrypter script" не находится в реальной системе, используется только зашифрованный файл конфигурации. Кто-то, кто анализирует код, может легко разбить шифрование, проанализировав код, но при необходимости вы можете скомпилировать его в EXE.