Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 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. Проверка пароля в системе контроля версий, где его могут увидеть другие разработчики или даже публика.
  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 сможет их прочитать, но с этим ничего не поделаешь.

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

Ответ 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.