Я пишу небольшой скрипт Python, который будет периодически извлекать информацию из сторонней службы, используя комбинацию имени пользователя и пароля. Мне не нужно создавать что-то на 100% пуленепробиваемое (100% вообще существует?), Но я хотел бы обеспечить хорошую меру безопасности, чтобы, по крайней мере, кому-то понадобилось бы много времени, чтобы это сломать.
У этого скрипта не будет графического интерфейса, и он будет запускаться периодически cron
, поэтому вводить пароль каждый раз, когда он запускается для расшифровки, на самом деле не сработает, и мне придется хранить имя пользователя и пароль либо в зашифрованном файле, либо в зашифрованном виде. в базе данных SQLite, что было бы предпочтительнее, поскольку я все равно буду использовать SQLite, и мне может потребоваться отредактировать пароль в какой-то момент. Кроме того, я, вероятно, оберну всю программу в EXE, поскольку на данный момент он предназначен исключительно для Windows.
Как я могу безопасно сохранить комбинацию имени пользователя и пароля для периодического использования в cron
работе?
источник
Ответы:
Я рекомендую стратегию, аналогичную ssh-agent . Если вы не можете использовать ssh-agent напрямую, вы можете реализовать что-то подобное, чтобы ваш пароль хранился только в ОЗУ. В задании cron можно было настроить учетные данные для получения фактического пароля от агента каждый раз, когда он запускается, использовать его один раз и немедленно отменять ссылку с помощью
del
оператора.Администратор по-прежнему должен вводить пароль для запуска ssh-agent во время загрузки или в другом месте, но это разумный компромисс, позволяющий избежать хранения пароля в виде простого текста где-нибудь на диске.
источник
Библиотека связки ключей 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)
Элементы зашифрованы с использованием учетных данных операционной системы пользователя, поэтому другие приложения, запущенные в вашей учетной записи, смогут получить доступ к паролю.
Чтобы немного скрыть эту уязвимость, вы можете каким-либо образом зашифровать / скрыть пароль перед сохранением его в связке ключей. Конечно, любой, кто нацелен на ваш скрипт, просто сможет посмотреть на источник и выяснить, как расшифровать / расшифровать пароль, но вы, по крайней мере, предотвратите удаление каким-либо приложением всех паролей в хранилище и получение вашего. .
источник
keyring
Поддерживает ли получение как имени пользователя, так и пароля?get_password
имени пользователя. Хотя, я думаю, вам следует начать ответ с оригинального упрощенного примераkeyring.set_password()
иkeyring.get_password()
keyring
не является частью стандартной библиотеки Pythonkeyring
безопасно удалить пароль из журналов и послесловий в памяти?После поиска ответов на этот и связанные с ним вопросы я собрал некоторый код, используя несколько из предложенных методов для шифрования и сокрытия секретных данных. Этот код предназначен специально для случаев, когда сценарий должен запускаться без вмешательства пользователя (если пользователь запускает его вручную, лучше всего, чтобы они вводили пароль и сохраняли его только в памяти, как подсказывает ответ на этот вопрос). Этот метод небезопасен; По сути, сценарий может получить доступ к секретной информации, поэтому любой, у кого есть полный доступ к системе, имеет сценарий и связанные с ним файлы и может получить к ним доступ. Что это делает, 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
Безопасность этого метода была бы значительно улучшена, если бы для секретных файлов были установлены разрешения операционной системы, позволяющие только самому сценарию читать их, и если бы сам сценарий был скомпилирован и помечен только как исполняемый (не читаемый). Кое-что из этого можно автоматизировать, но я не стал беспокоиться. Вероятно, потребуется настроить пользователя для сценария и запустить сценарий от имени этого пользователя (и установить право собственности на файлы сценария для этого пользователя).
Я хотел бы получить любые предложения, критические замечания или другие уязвимые места, о которых может подумать каждый. Я новичок в написании криптокода, поэтому то, что я сделал, почти наверняка можно улучшить.
источник
Есть несколько вариантов для хранения паролей и других секретов, которые должна использовать программа Python, в частности программа, которая должна работать в фоновом режиме, где она не может просто попросить пользователя ввести пароль.
Проблемы, которых следует избегать:
Вариант 1: SSH
Это не всегда вариант, но, вероятно, лучший. Ваш закрытый ключ никогда не передается по сети, 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 допускает комментарии.
При работе с файлами конфигурации следует учитывать три момента:
~/.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 на чтение из этих переменных среды.
источник
os.getenv()
. Как нам это сделать, если код распространяется? Если код загружен другим разработчиком, как он / она должен убедиться, что переменные среды уже установлены для него?os.getenv()
@a_sid, чтобы код работал, по крайней мере, для пользователя, который не установил переменные среды. Если подходящего значения по умолчанию нет, при получении выдайте явную ошибкуNone
. Кроме того, оставьте четкие комментарии в файле настроек. Если я что-то неправильно понял, предлагаю вам задать отдельный вопрос.Нет особого смысла пытаться зашифровать пароль: у человека, от которого вы пытаетесь его скрыть, есть скрипт Python, который будет иметь код для его расшифровки. Самый быстрый способ получить пароль - это добавить оператор печати в скрипт Python непосредственно перед тем, как он использует пароль со сторонней службой.
Так что сохраните пароль в виде строки в скрипте, и base64 закодирует его так, чтобы просто прочитать файл было недостаточно, а затем закончите это дело.
источник
Я думаю, лучшее, что вы можете сделать, - это защитить файл сценария и систему, в которой он работает.
В основном делайте следующее:
источник
операционные системы часто поддерживают защиту данных для пользователя. в случае с окнами это выглядит как http://msdn.microsoft.com/en-us/library/aa380261.aspx
вы можете вызвать win32 apis из 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)
Мой скрипт работает в физически защищенной системе / комнате. Я шифрую учетные данные с помощью «скрипта шифрования» в файл конфигурации. А потом расшифровать, когда мне нужно их использовать. «Сценарий шифрования» отсутствует в реальной системе, есть только зашифрованный файл конфигурации. Кто-то, кто анализирует код, может легко взломать шифрование, проанализировав код, но вы все равно можете скомпилировать его в EXE при необходимости.
источник