Изображение : freeGraphicToday, via Pixabay. CC0.
В условиях растущих требований к безопасности создание и хранение паролей может вызвать вопросы не только для пользователей, но и у разработчиков и системных администраторов. Специалисты и другие осведомлённые люди знают, что пароли нужно хранить в зашифрованном виде. Уже на этапе ввода символы пароля нужно скрывать от любых глаз (даже от того человека, который его вводит). Всегда ли мы можем выполнить хотя бы эти требования?
Я единственный пользователь своего ноутбука, а на его борту крутится ОС семейства Linux. Поэтому меня не беспокоят пользователи, которые могут случайно или неслучайно посмотреть мои конфигурационные файлы, работая на этом же компьютере. Я решил заморочиться и повысить безопасность своего личного ноутбука, и на то есть свои причины. Да, я шифрую свой домашний каталог, но как только вхожу в систему, любой пароль, хранящийся в виде простого текста в файле конфигурации, потенциально может быть уязвим для чересчур любопытных глаз.
К тому же, я использую почтовый клиент Mutt. Он позволяет мне читать и составлять электронные письма прямо в Linux-терминале. Мне удобно, мне нравится. Правда, ему нужно, чтобы я хранил пароль в файле конфигурации (.mutt), либо всё время вводил пароль в интерактивном режиме. Поэтому я ограничил права доступа к моему конфигурационному файлу Mutt, чтобы его мог видеть только я.
Но есть ещё один важный момент: я периодически пишу технические статьи, составляю туториалы, помогаю людям в сообществе и публикую много своего кода в общедоступных репозиториях, публикую скриншоты своего экрана, часто показываю что-то на примере своей рабочей системы. Если по недосмотру меня угораздит засветить в Интернете (или где-то ещё) данные (и в том числе пароли) из моих конфигурационных файлов, это бросит неприятную тень на мою репутацию и безопасность. Так что надо подстраховаться.
Ну и, если вдруг злоумышленник завладеет моим ноутбуком или каким-то другим образом получит доступ в систему, он не сможет получить мой пароль без боя, просто запустив cat для просмотра логов и конфигов.
Поиск решения задачи
Я решил, что лучший способ защитить мой пароль в Mutt — ввести пароль с клавиатуры, сохранить его в зашифрованном файле GPG, написать на Python скрипт расшифровки для моего GPG-пароля, ну и заодно обеспечить передачу пароля Mutt в скрипт offlineimap, который я использую для локальной синхронизации моего ноутбука с почтовым сервером.
Из подзаголовка статьи ясно, что я буду использовать модули python-gnupg и getpass. Модуль Python python-gnupg — это обёртка для инструмента шифрования gpg. Учтите, python-gnupg не следует путать с модулем под названием gnupg. GnuPG (GPG) — это утилита шифрования для Linux, и я использую её с 2009 года или около того. С ней я чувствую себя комфортно и верю в её безопасность.
Получить пользовательский ввод с помощью Python довольно просто. Вы вызываете input, и всё, что введёт пользователь сохраняется в переменной:
print("Enter password: ")
myinput = input()
print("You entered: ", myinput)
И в этом случае есть одна громадная проблема: когда я ввожу пароль в терминале, всё, что я набираю, видно всем, кто смотрит через моё плечо или просматривает историю моего терминала:
$ ./test.py
Enter password: my-Complex-Passphrase
Написание скрипта с python-gnupg и getpass
Как это часто бывает, ничего самому писать не надо, потому что уже существует модуль Python, который позволяет решить проблему. Это модуль getpass4. С точки зрения пользователя он ведёт себя точно так же, как любое стандартное приглашение к вводу, за исключением того, что не отображает введённые символы.
Установим оба модуля с помощью pip:
$ python -m pip install --user python-gnupg getpass4
У меня получился вот такой скрипт для создания пароля с невидимым вводом и расшифровкой:
#!/usr/bin/env python
# by Seth Kenlon
# GPLv3
# install deps:
# python3 -m pip install --user python-gnupg getpass4
import gnupg
import getpass
from pathlib import Path
def get_api_pass():
homedir = str(Path.home())
gpg = gnupg.GPG(gnupghome=os.path.join(homedir,".gnupg"), use_agent=True)
passwd = getpass.getpass(prompt="Enter your GnuPG password: ", stream=None)
with open(os.path.join(homedir,'.mutt','pass.gpg'), 'rb') as f:
apipass = (gpg.decrypt_file(f, passphrase=passwd))
f.close()
return str(apipass)
if __name__ == "__main__":
apipass = get_api_pass()
print(apipass)
Сохраните файл как password_prompt.py, если хотите попробовать скрипт у себя. Если вместе с ним вы хотите использовать offlineimap, укажите в конфигурационном файле .offlineimaprc имя и путь к скрипту с паролем (у меня это ~/.mutt/password_prompt.py). Правда, там нужно сделать ещё кое-что, но об этом позже.
Тестирование скрипта с gpg
Надеюсь, у вас уже установлен gpg, так как сейчас мы будем создавать зашифрованный файл пароля и тестировать скрипт.
Шифруем:
$ echo "hello world" > pass
$ gpg --encrypt pass
$ mv pass.gpg ~/.mutt/pass.gpg
$ rm pass
Запускаем созданный ранее скрипт:
$ python ~/.mutt/password_prompt.py
Enter your GPG password:
hello world
Ура! При вводе ничего не отображается, но если вы правильно введёте пароль (нужно ввести hello world), на следующей строке вы увидите тестовое сообщение «hello world». Оно же и является паролем, полученным в результате расшифровки файла ~/.mutt/pass.gpg. Значит, скрипт работает правильно.
Интеграция с offlineimap
Я выбрал Python, потому что знал, что offlineimap может вызывать скрипты, написанные на Python. Если вы уже пользуетесь offlineimap, вы поймете, что единственная необходимая «интеграция» сводится к изменению двух строк в вашем файле .offlineimaprc (точнее — к добавлению одной строки и изменению другой).
Сначала добавьте строку, ссылающуюся на файл нашего скрипта:
pythonfile = ~/.mutt/password_prompt.py
Теперь вместо пароля в строке с remotepasseval после знака «=» вызовите функцию get_api_pass(), которая живёт в скрипте password_prompt.py:
remotepasseval = get_api_pass()
Всё! Теперь никто не сможет прочитать пароль из вашего конфигурационного файла!
Безопасность даёт свободу
Иногда кажется, что у меня паранойя: я много думаю о тонкостях обеспечения безопасности на моём личном компьютере. Действительно ли SSH конфиг должен иметь разрешения chmod 600? Действительно ли имеет значение, что пароль электронной почты находится в конфигурационном файле, спрятанном в скрытой папке, которая называется, как ни странно, .mutt? Хотя написать подобный скрипт на Python можно и для других конфигурационных файлов.
Зная, что в моих файлах конфигурации отсутствуют незашифрованные конфиденциальные данные, мне намного проще публиковать файлы в общедоступных репозиториях Git, копировать и вставлять сниппеты на форумах и делиться своими знаниями в форме актуальных, рабочих файлов конфигурации. С этой точки зрения, повышение уровня безопасности облегчило мне жизнь. А с таким количеством Python-модулей на все случаи жизни, сделать это было достаточно легко.
Аренда облачного сервера с быстрыми NVMе-дисками и посуточной оплатой у хостинга Маклауд.
alexxisr
Я правильно понял — чтобы получить пароль из файла, нужно ввести пароль от этого файла с клавиатуры? каждый раз?
Есть ли какой-то более-менее стандартный способ защитить пароль в конфигах без ручного ввода? Только хранить где-то захардкоженный ключ?
dvserg
Пароль в конфигах, обычно, хранится в виде хэша.
alexxisr
Если по хешу можно подключиться к базе данных, то чем это отличается от хранения просто пароля?
olegmityagin
Так себе идея хранить хеш. Из хеша крайне затруднительно восстановить пароль для последующего использования, т.к. "Фарш невозможно провернуть назад" (с).
dolfinus
Только если это конфиг сервера, вроде nginx. В конфиге клиента пароль не может быть хэшированным по определению - либо в открытом виде, либо зашифрованный.