Мені потрібно надійно зберігати ім’я користувача та пароль у Python, які у мене варіанти?


96

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

Цей скрипт не матиме графічного інтерфейсу і буде періодично запускатись cron, тому введення пароля кожного разу, коли він запускається для дешифрування речей, насправді не спрацює, і мені доведеться зберігати ім’я користувача та пароль або в зашифрованому файлі, або в зашифрованому в базі даних SQLite, що було б кращим, оскільки я все одно буду використовувати SQLite, і я можливо, доведеться відредагувати пароль в якийсь момент. Крім того, я, мабуть, оберну всю програму в EXE, оскільки на даний момент це виключно для Windows.

Як я можу надійно зберігати комбіновані ім’я користувача та пароль для періодичного використання через cronзавдання?


Дивіться також : stackoverflow.com/questions/157938
dreftymac

Відповіді:


19

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

Адміністратору все ще потрібно ввести пароль для запуску ssh-agent, під час завантаження чи будь-якого іншого, але це розумний компроміс, який дозволяє уникнути збереження простого тексту пароля в будь-якому місці на диску.


2
+1, це має багато сенсу. Я завжди міг створити для нього інтерфейс, який по суті запитує у користувача його пароль при завантаженні, таким чином, він ніколи не зберігається на диску і захищений від сторонніх очей.
Naftuli Kay

54

Бібліотека ключів 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підтримує отримання як імені користувача, так і пароля?
Стевойсяк

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

keyringне є частиною стандартної бібліотеки python
Ciasto piekarz

@Ciastopiekarz Чи щось із відповіді змусило вас повірити, що вона є частиною стандартної бібліотеки?
Дастін Вайатт,

Чи keyringбезпечно чистити пароль із журналів та післямов після пам’яті?
Кебман,

26

Переглянувши відповіді на це та відповідні запитання, я зібрав деякий код, використовуючи декілька із запропонованих методів шифрування та затемнення секретних даних. Цей код призначений спеціально для випадків, коли сценарій повинен запускатися без втручання користувача (якщо користувач запускає його вручну, краще, щоб він ввів пароль і зберігав його лише в пам'яті, як пропонується у відповіді на це запитання). Цей метод не надто безпечний; по суті, сценарій може отримати доступ до секретної інформації, тому кожен, хто має повний системний доступ, має сценарій та пов'язані з ним файли і може отримати до них доступ. Що це робить ідентифікатор, затемнює дані від випадкового огляду та залишає самі файли даних у безпеці, якщо вони перевіряються окремо або разом без сценарію.

Мотивацією цього є проект, який опитує деякі мої банківські рахунки для моніторингу транзакцій - мені потрібно, щоб він працював у фоновому режимі, не повторно вводячи паролі кожну хвилину чи дві.

Просто вставте цей код у верхній частині сценарію, змініть 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

Безпека цього методу була б значно покращена, якби на секретних файлах були встановлені дозволи ос, щоб сценарій дозволяв їх самому читати, і якщо сам сценарій був скомпільований та позначений як лише виконуваний (не читається). Дещо з цього можна було б автоматизувати, але я не турбувався. Ймовірно, потрібно буде налаштувати користувача для сценарію та запустити скрипт як цей користувач (і встановити право власності на файли сценарію для цього користувача).

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


25

Існує кілька варіантів зберігання паролів та інших секретів, які програма 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)

Шукайте, як встановити змінні середовища у вашій операційній системі, та розгляньте можливість запуску служби під її власним обліковим записом. Таким чином, у вас немає конфіденційних даних у змінних середовища, коли ви запускаєте програми у власному обліковому записі. Налаштовуючи ці змінні середовища, подбайте про те, щоб інші користувачі не могли їх прочитати. Наприклад, перевірте дозволи файлів. Звичайно, будь-які користувачі з дозволом адміністратора зможуть їх прочитати, але це не допоможе.

Варіант 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 це, де б я не закінчив зберігати його?
Naftuli Kay

Я погоджуюсь з тим, що "шифрування" пароля не допомагає, оскільки звичайний текст пароля в будь-якому випадку повинен бути отриманий в автоматизованому режимі, і тому його слід отримати з будь-якого збереженого. Але є життєздатні підходи.
wberry

Думаючи, я впізнав ваше ім’я, ви були на панелі початківців та експертів у TalkPython, як новачок, ваше повідомлення справді резонувало зі мною, дякую!
Манакін

7

Я думаю, що найкраще, що ви можете зробити, це захистити файл сценарію та систему, на якій він працює.

В основному роблять наступне:

  • Використовувати дозволи файлової системи (chmod 400)
  • Надійний пароль для облікового запису власника в системі
  • Зменшити можливість компрометації системи (брандмауер, вимкнення непотрібних служб тощо)
  • Видаліть права адміністратора / root / sudo для тих, хто цього не потребує

На жаль, це Windows, я загортаю його в EXE, і мені потрібно буде міняти пароль так часто, настільки жорстко кодуючи, це не буде варіантом.
Naftuli Kay

1
Windows все ще має дозволи на використання файлової системи. Збережіть пароль у зовнішньому файлі та видаліть доступ усіх, крім вашого власного. Можливо, вам також доведеться скасувати їхні адміністративні привілеї.
Кори Д

Так, використання дозволів - це єдиний надійний варіант захисту тут. Очевидно, що будь-який адміністратор все одно зможе отримати доступ до даних (принаймні у Windows / звичайних дистрибутивах Linux), але тоді це битва вже програна.
Voo

Це правда. Коли дешифрування пароля автоматизоване, то це так само добре, як наявність пароля в простому тексті. Справжня безпека полягає в блокуванні облікового запису користувача з доступом. Найкраще, що можна зробити, - це надати дозволи лише на читання лише цьому обліковому запису користувача. Можливо, створити спеціального користувача, спеціально і лише для цієї послуги.
Сеперо,

1

операційні системи часто мають підтримку захисту даних для користувача. у випадку з Windows це схоже на 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.