Вступление

Всем привет!

Сразу скажу: я не собираюсь претендовать на звание самого умного, просто хочу высказать ряд своих мыслей, которые являются ИМХО. Если вы с чем-то не согласны, прошу в комментарии, обсудим.

Эта статья будет разделена на две части. В первой я рассмотрю некоторые популярные решения для менеджеров паролей и расскажу, что мне в них не нравится и почему я решил придумать что-то новое. Во второй - наиболее безопасный менеджер на мой взгляд менеджер паролей, а также описание алгоритмов, которые буду использовать при его реализации. Под словом "безопасный" я понимаю следующее: необходимо сделать всё, чтобы никто не мог получить доступ к паролям пользователя, даже если придется лишить пользователя доступа к его паролям.

Популярные менеджеры паролей

1) KeePass - данный менеджер паролей идет первым почти во всех обзорах. Но при этом, на мой взгляд имеет проблемы, основные из них:

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

Во-вторых, что еще хуже на мой взгляд и было частично освещено в прошлом пункте: для доступа к паролям требуется... Пароль. Какая-то рекурсия получается.

В-третьих, он отображает все логины на всех сервисах, данные к которым хранятся в менеджере. Удобно, но на мой взгляд, это может упростить процесс доступа к учетной записи пользователя, даже если он сменил пароль. Простой пример: нарушитель получил доступ к старой версии файла и смог его расшифровать, в базе доступны все имена и пароли пользователя, поэтому нарушителю достаточно перебрать пароли на других сервисах, на случай, если пользователь просто использует одни и те же пароли. Конечно, прятать полный список сервисов и логинов - выстрел в ногу пользователя, но посмотрите на название статьи.

2) Bitwarden - облачный менеджер паролей, сервер которого можно развернуть локально. Вроде бы все в нем идеально: сквозное шифрование, двухфакторная аутентификация. Но есть три "но":

Первое и второе "но" точно такие же, как во втором и третьем пунктах предыдущего менеджера, так что не буду повторяться.

Третье "но" относится к тому, что можно легко получить доступ к паролям с различных устройств, а не только с того, с которого были пароли созданы. Да, можно настроить 2FA (которая не является обязательной), но что, если сольют базу данных самого Bitwarden? Зависит от типа 2FA, но в случае, например, приложений-аутентификаторов (например Google Authentificator), до смены сервера 2FA ваш аккаунт, возможно, будет защищен только мастер-паролем.

Так что же я считаю по-настоящему безопасным менеджером паролей?

Сейчас будет рассказана утопическая ситуация, с которой кто-то посмеется, кто-то поплачет, кто-то скажет, что я сошел с ума, но приступим:

  1. Хранение паролей в облаке, причем пароли защищены сквозным шифрованием.

  2. Доступ к паролю можно получить только с того же устройства, с которого он был создан

  3. Отсутствие мастер-паролей для входа в систему.

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

  5. Менеджер паролей не должен раскрывать список ваших сервисов и логинов на них.

  6. Менеджер паролей не должен иметь возможности восстановить доступ к учетной записи пользователя в случае утери данных доступа. В противном случае нарушитель может будет воспользоваться этим, чтобы получить доступ к паролям, восстановив данные для входа.

  7. Менеджер паролей не должен знать какая запись с паролями в базе принадлежит какому пользователю (опционально). Тут я имею в виду то, что даже пройдя все круги ада, слив базу, расшифровав ее и пароли, хранящиеся в ней, нельзя было идентифицировать учетную запись к которой принадлежит пароль, либо пользователя владеющего им.

Звучит как выстрел в ногу пользователю, если учесть, что надежно идентифицировать устройство можно только по его железу, а это значит, что пользователь, использующий такой менеджер паролей потеряет доступ к паролям в случае, условно, выхода из строя жесткого диска. Также отсутствие восстановления данных для входа не скажется позитивно на опыте пользователя работы с данным менеджером.

Алгоритмы реализации менеджера паролей

А сейчас я предложу несколько идей алгоритмов для того, чтобы сделать реализацию такого менеджера возможной. Для начала введу три определения:

1) Hardware info - строка, содержащая данные об устройстве (такие как наименование процессора, видеокарты. объем оперативной памяти, серийные номера накопителей и т.д.)

2) Hardware key - ключ, генерируемый на основании данных об устройстве. Например, можно вычислять его так: SHA512(Hardware info)

3) Software key - информация о текущей версии клиентского ПО (например, контрольная сумма файлов приложения)

Зная три этих основных определения можно перейти непосредственно к самим алгоритмам.

1) Регистрация пользователя (устройства)

a) На сервер отправляется hardware key устройства.
b) Сервер генерирует случайную 512-битовую последовательность (regSalt).
c) Сервер вычисляет totpKey = SHA512(hardware key | regSalt) и заносится в базу данных для 2FA.
d) Сервер возвращает клиенту totpKey, который пользователь должен где-то надежно сохранить, чтобы в дальнейшем генерировать одноразовые коды для входа в систему.

2) Аутентификация пользователя (устройства)

a) Пользователь вводит одноразовый код, который отправляется на сервер вместе с hardware key
b) Сервер ищет в базе данный hardware key и сравнивает текущее значение одноразового кода (totp) для totpKey с пришедшим от клиента.
c) В случае, если одноразовые коды совпали, сервер возвращает клиенту два JWT-токена: один для осуществления действий с паролями, второй - для повторной аутентификации по истечению времени первого токена.

В пунктах 1 и 2 аутентификация реализована таким образом, что пользователю не нужно иметь мастер-ключ для аутентификации в сервисе.

3) Создание/редактирование пароля на сервере

Из-за ряда особенностей предложенного мной метода хранения данных об учетных записей пришлось пойти на некоторые усложнения алгоритма, так что стоит внимательнее следить за руками словами.

Сервер на все входящие запросы в данном блоке проверяет валидность токена.

1) Клиент отправляет на сервер информацию о сервисе (название), для которого производится получение пароля.
2) Сервер возвращает соль (salt), принадлежащую данному сервису (псевдослучайное 512- битовое значение, генерирующееся в момент регистрации сервиса, т.е. имеющее постоянное значение).
3) Клиент вычисляет идентификатор пароля следующим образом (login - логин пользователя на сервисе):

Id_{pass} = SHA512(salt|login|hardware\ key)

4) Клиент генерирует модуль для алгоритма RSA2048 на основании идентификатора пароля (модуль известен серверу, потому что он может проделать все те же самые действия для идентификатора пароля):

num = PBKDF2(SHA512,Id_{pass}  [1:448], Id_{pass}  [449:512],200,1024)\ mod (p \cdot q)

, где num - последовательно генерируемые числа (последовательности бит). Произведение первых двух различных простых чисел будет являться модулем.
5) Клиент рассчитывает открытую экспоненту e (которая известна только ему):

a) Вычисляется значение, на основании которого будет генерироваться открытая экспонента:

Hash_e = SHA512(login | hardware\ info |software\ key)

Заметим, что hardware info, как и login неизвестны серверу.

b) Вычисляется открытая экспонента e:

e = PBKDF2(SHA512,Hash_e  [1:448],Hash_e  [449:512],2000,2048)\ mod (p \cdot q)

c) Если НОД(e, (p-1)*(q-1)) != 1, берем следующие генерируемые 2048 бит до тех пор, пока НОД не станет равным 1.

6) Вычисляется зашифрованный пароль:

EncPass = password^e\ mod\ (p \cdot q)

7) Вычисляется дополнительное открытое основание totpE (напомню, что totp хранится в зашифрованном виде в токене):

Hash_{totp} = SHA512(totp\ |Id_{pass})

После чего вычисляется totpE, аналогично с вычислением открытого основания, которое знает только клиент.

8) На сервер отправляется тройка:

(Id_{pass}, EncPass^{totpE}\ mod\ (p \cdot q), software\ key)

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

9) Сервер получает тройку данных, вычисляет totpD такое, что:

totpE \cdot totpD \equiv 1\ mod((p-1) \cdot (q-1))

И вычисляет зашифрованный пароль:

EncPass = (EncPass^{totpE})^{totpD}\ mod\ (p \cdot q)

После этого сервер заносит в базу тройку:

(Id_{pass}, EncPass, software\ key)

Дополнительное шифрование необходимо для того, чтобы гарантировать защиту от атаки MITM, в случае, если у нарушителя есть доступ к hardware info (например, если компьютер пользователя заражен): пароли дополнительно шифруются с помощью RSA с тем же модулем, но с другими открытой и закрытой экспонентами, которые генерируются с помощью одноразового кода, который был введен при входе в систему в данном сеансе. Чтобы нельзя было быстро перебрать все открытые экспоненты для каждого значения totpKey (а их всего миллион), используется алгоритм PBKDF2, который сильно замедляет скорость перебора ключей.

Но в использовании PBKDF2 есть и проблема: в среднем на генерацию одной пары p и q данным алгоритмом требуется 2-4 секунды. Соответственно это еще один выстрел пользователю в колено: можно ожидать момента создания/получения пароля несколько эти 3-5 секунд при условии генерации ключа на клиенте во время ожидания ответа от сервера.

4) Получение пароля с сервера:

В каком-то роде для получения пароля с сервера выполняются все те же самые действия, что для его создания или редактирования.

Напомню, что в данном случае сервер всегда проверяет валидность токена при входящих запросах (производит авторизацию пользователя)

1) Клиент отправляет запрос на сервер с указанием идентификатора пароля.

2) Сервер ищет в базе запись с данным идентификатором. Если записи не найдено, возвращает Not Found, иначе переходит на следующий шаг.

3) Сервер генерирует ключ для дополнительного шифрования с помощью RSA. Модуль вычисляется также, как в предыдущем разделе, а открытая экспонента следующим образом:

Hash_{totp} = SHA512(totp\ |Id_{pass})

После чего вычисляет с помощью PBKDF2 открытую экспоненту также, как в предыдущем пункте.

4) Сервер отправляет клиенту следующую пару данных:

(EncPass^{totpE}\ mod\ (p \cdot q), software\ key)

, где software key - информация о версии программы, с помощью которой был создан в базе данный пароль (показывает клиенту необходимость обновления пароля в базе, если он был создан с помощью более ранней версии программы).

5) Клиент вычисляет модуль, открытое основание и totpE (у клиента totp хранится в оперативной памяти после успешной аутентификации на сервере). После этого клиент вычисляет закрытое основание d такое, что:

(totpE \cdot e) \cdot d \equiv 1\ mod ((p-1) \cdot (q-1))

6) Клиент вычисляет пароль возведением полученного дважды зашифрованного значения в степень d:

password = (EncPass^{totpE})^d \equiv password^{e \cdot totpE \cdot d} \equiv password\ mod (p \cdot q)

7) В случае, если пароль был создан более ранней версией программы, клиент обновляет пароль на сервере.

Что дальше?

В дальнейшем я собираюсь собрать эту систему воедино, написав сервер на ASP.NET Core и десктопное приложение на WPF, после чего выложить их в open-source.Сервер по большей части уже завершен, следующая часть будет посвящена ему.

Заключение

Таким образом мы успешно выстрелили пользователю в колено создали алгоритмы, которые помогут противостоять большому количеству атак. Приведу основные из них:

1) Труднодоступность аутентификации постороннего пользователя (о полной невозможности, конечно, говорить нельзя, но чтобы аутентифицироваться в системе необходимо иметь доступ либо к серверу, либо одновременно к устройству пользователя и устройству-аутентификатору).

2) Труднодоступность получения пароля нарушителем в случае успешной аутентификации. Для получения пароля обязательно необходимо иметь доступ к устройству пользователя, иначе дешифровать пароль будет достаточно проблематично (сервер не знает открытую/закрытую экспоненту ключа). Поэтому вкупе с первым фактором, нарушителю для получения пароля требуется доступ к двум из трех устройств в системе. Также нарушителю необходимо знать название сервиса, для которого необходимо получить пароль, и логин пользователя для данного сервиса.

3) Усложнение атаки man-in-the-middle. Тут зависит от возможности доступа нарушителя к устройству пользователя: если к нему есть доступ (например стоит вредоносное ПО, которое получает данные об устройстве), то дополнительное шифрование с помощью totp помогает защитить пароль в случае раскрытия только сообщения с паролем. В случае, если нарушитель видит весь трафик между устройством пользователя и сервером, то предпринятые меры не являются достаточными: как одноразовый totp-код, так и идентификатор пароля будут известны нарушителю.

4) Защита от получения паролей владельцем сервера: несмотря на то, что серверу известен модуль ключа RSA, ему не известна открытая или закрытая экспонента, поэтому дешифровать пароль не представляется возможным.

5) Защита от получения списка сервисов и логинов пользователя. Как было сказано в тексте статьи это невозможно по двум причинам: пароли хранятся непривязанными к конкретному пользователю. Информация о логине и сервисе для конкретного сервиса являются лишь частью хэшируемых данных, поэтому даже зная идентификатор пароля не представляется возможным получить первые.

Но при этом данная система вряд ли пригодится обычным пользователям, потому что в случае изменения конфигурации устройства, пароли канут в небытие. Основное применение я вижу, например, в компаниях, в которых обновление оборудования происходит редко, а хранить предлагаю только пароли, которые во-первых возможно быстро восстановить, а во-вторых действительно важные пароли, которые необходимо защитить. Иначе пользователю в случае отказа какой-то части устройства придется восстанавливать/вспоминать все пароли.

Комментарии (28)


  1. igrishaev
    19.06.2024 06:20
    +22

    Хранение паролей в облаке

    Что вы имеете в виду под "облаком"? Чужой компьютер? Ноду на Digital Ocean?

    Доступ к паролю можно получить только с того же устройства, с которого он был создан

    Если два года назад я создал пароль на ноуте, а теперь он понадобился мне в аэропорту, а с собой только телефон -- иду я нафиг?

    Но на мой взгляд, хранить данные локально - самая большая ошибка, даже в зашифрованном виде

    То, что для вас является удаленным файлом, является локальным относительно "облака". Его так же могут слить.


    1. SpiderEkb
      19.06.2024 06:20
      +6

      Веселье начнется когда поменяешь комп/ноут/телефон. Все пароли недоступны.

      Все что лежит у тебя на компе - твое. Все что лежит в облаке - не твое. Доступ к нему может пропасть по куче разных причин.


      1. nyarovoy Автор
        19.06.2024 06:20

        Поэтому в конце статьи и сделал оговорку, что хранить пароли от всего подряд не стоит, ровно как и рядовому пользователю будет работать не совсем удобно именно по причине потери доступа к паролям.


        1. SpiderEkb
          19.06.2024 06:20
          +7

          хранить пароли от всего подряд не стоит, ровно как и рядовому пользователю будет работать не совсем удобно

          Тогда зачем все это нужно?


    1. nyarovoy Автор
      19.06.2024 06:20

      Что вы имеете в виду под "облаком"? Чужой компьютер? Ноду на Digital Ocean?

      В данном случае - развернутый, например, компанией сервер. Как он развернут не так важно на деле в алгоритмической реализации: главное - разнести данные между устройством, на котором пароли используются, и устройством, на которых пароли хранятся.

      Если два года назад я создал пароль на ноуте, а теперь он понадобился мне в аэропорту, а с собой только телефон -- иду я нафиг?

      Как описал в конце, эта система для обычных пользователей не особо подходит и является выстрелом в ногу пользователя. С этой претензией вообще не спорю, что неудобно и не является полезным.

      То, что для вас является удаленным файлом, является локальным относительно "облака". Его так же могут слить.

      До общей реализации, как я понимаю, вы не дошли. Пароли шифруются ключом, зависимым от данных устройства, на котором пароль был создан. Без привязки к конкретному устройству. Поэтому даже если они сольются, расшифровать их невозможно. В то же время, если хранить их на локальном компьютере, для достижения цели этого требуется мастер-пароль, от использования которого я стремлюсь отказаться. Да, мастер-пароль будет секретом для TOTP. Но конечному пользователю их не нужно запоминать нигде, кроме приложения-аутентификатора.


      1. igrishaev
        19.06.2024 06:20
        +7

        У вас несколько раз повторяется тезис, что хранить зашифровнные данные локально почему-то небезопасно, а в облаке -- безопасно. И ваше определение "облака" -- цитирую, "развернутый, например, компанией сервер", -- никак не оъясняет, где безопасность.

        Поедлюсь своей схемой. У меня unix pass, пароли шифруются ключом GPG. Все хранится локально и синхронизируется черех приватный реп на Гитхабе. В данном случе это просто точка обмена, и даже если я сделаю репозиторий публичным, расшифровать ничего не выйдет, потому что GPG. Ключ я распечатал на листах А4 и храню в нескольких местах. Пароль от него держу в голове.


        1. nyarovoy Автор
          19.06.2024 06:20

          Наверное, я не совсем правильно выражаюсь, либо мы не понимаем друг друга. Я имею в виду, что хранить пароли без использования мастер-пароля небезопасно. Да, если использовать мастер-пароль для доступа к паролям, то все становится гораздо проще и безопаснее. Но именно от мастер-пароля я стремлюсь уйти. Да, не до конца: он представлен секретом для TOTP.

          Про безопасность подробно описано в конце статьи, но я повторю кратно. Система использует три устройства для защиты паролей: само устройство пользователя, устройство с приложением-аутентификатором, а также сервер, на котором хранятся данные. Для получения возможности несанкционированно расшифровать пароль необходимо иметь доступ к устройству, с которого был создан пароль, а также еще одному из двух оставшихся. И при всем этом необходимо знать в каких сервисах какой логин пользователь использует.

          Почему же это безопаснее, если рассматривать хранение данных локально без использования мастер-ключа (это не ваш случай, конечно, но я беру сравнимые случаи)? Потому что выработку ключа шифрования может повторить любое вредоносное ПО и получить доступ к паролям.

          В случае использования мастер-ключа, не спорю, безопасность все равно обеспечивается, но ее уровень зависит от сложности ключа. Согласитесь, пользователю вряд ли понравится вводить с бумажки что-то сложное.


      1. SpiderEkb
        19.06.2024 06:20
        +3

        Пароли шифруются ключом, зависимым от данных устройства, на котором пароль был создан.

        Ок. Пароли зашифрованы ключом, зависимым от данных телефона. Телефон сломался/потерялся/украден/просто надоел. Что дальше? Вся база паролей превратилась в тыкву?

        Может проще зашифровать ее каким-то сверхдлинным ключом и сразу его забыть?


        1. nyarovoy Автор
          19.06.2024 06:20

          База для данного пользователя превратилась в тыкву, увы. Доступ не будет получен, потому что нужно также получить доступ к аутентификатору


          1. SpiderEkb
            19.06.2024 06:20
            +4

            Вы считаете это нормальным?

            Вообще, замена телефона достаточно типичный сценарий. Равно как и замена или апгрейд компа.

            И в каждой такой ситуации потеря базы паролей... Ну такое себе...

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


          1. SanSYS
            19.06.2024 06:20

            Если вы за облака и не хотите вводить пароль, но за привязку к железу, то вам нужен ноут с TPM и там хоть по отпечатку пальца разблокируйте ключ доступа к мастер паролю для расшифровки (простите за грубые неточности)

            Важный поинт – современные "облачные" менеджеры паролей уже это делают (1пасс, битварден), при том в случае утери – вы же знаете свой мастер пароль? Который всё же сложный, но для вас лично легко запоминаемый


  1. NikaLapka
    19.06.2024 06:20
    +1

    Где хранить пароли - там где удобно, куда есть быстрый доступ. Например, сейчас, в эпоху SSD и аппаратного ускорения шифрования, стало удобно шифровать носители информации, будь-то жёсткие диски, или флешки.. и часто при работе с ними у вас бывают и разные версии ОС и просто отсутствие Интернета, в этом случае пароль на флешке будет намного удобнее любого облака.

    Как хранить пароли - в зашифрованном контейнере, или зашифрованных каким-либо ключом, репцилированого во все возможные, удобные места хранения: на каждый диск, флешку, телефон, планшет, облако, почту, роутер, телевизор, утюг,..

    В чём хранить пароли - в чём нравится, хоть в .тхт, хоть на книжной полке.


    1. Wesha
      19.06.2024 06:20

      Где хранить пароли - там где удобно, куда есть быстрый доступ.

      Эмммм.. как насчёт в мозгу?


      1. SpiderEkb
        19.06.2024 06:20
        +4

        Ну вот пример. Только для работы мне требуется как минимум 3 пароля. Каждый минимум 12 символов, используются символы минимум 3-х групп из 4-х. Смена пароля не реже чем каждые 90 дней. На ввод правильного пароля 3 попытки, дальше учетка блокируется.

        Мало что хранить все это в мозгу несколько напрягает, так еще и придумывать их регулярно фантазии не хватит (поэтому - генератор).


        1. SanSYS
          19.06.2024 06:20

          Поддерживаю предыдущего оратора

          После того, как я попался менеджерам паролей и email relay (+pgp) – я честно даже логины не знаю, их более трёхста уже

          Предпочту помнить что-то более важное, чем кучку кредов


  1. kenomimi
    19.06.2024 06:20

    Для безопасного хранения паролей можно использовать какой-нибудь вариант реализации badusb, прилепив к нему сканер отпечатков или еще что-то такое, чтобы для паролей не вводить пароль. Да, для кодов запуска ядерных ракет не пойдет, но для 99.9% бытовых и рабочих нужд - идеально.

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


    1. nyarovoy Автор
      19.06.2024 06:20

      Со сканером отпечатков пальцев идея неплохая, я думал про нее. Но опять-таки, посмотрим на проблему слегка шире: где-то этот отпечаток пальца требуется хранить и создавать на его основании ключ доступа. И тут уже вопрос в том, насколько его реально выцепить. А еще в том, где пользователю взять сканер отпечатка пальцев, хотя эта проблема стоит в меньшей мере.


      1. SanSYS
        19.06.2024 06:20

        Сегодня у подавляющего большинства есть сканер биометрии, есть даже менеджер паролей, который стремится избавить всех пользователей от паролей добавив биометрию, см. демо https://youtu.be/RQHmljGydr0?si=NyDfMnRCtiXnjpfy


  1. d1rknwh1te3
    19.06.2024 06:20
    +2

    Если хочется максимальной безопасности, то проще разместить хранилище на локальной машине, у которой не будет доступа в сеть. Минус только в том, что придется вручную вводить его с другого монитора. Можно, конечно реализовать печать пароля на термоленте, с последующим считыванием его по OCR (можно и считывать с монитора), но это из разряда шапочки из фольги.

    Но по-моему мнению безопаснее (для единичного пользователя) все-таки хранить у себя локально, потому что шанс того, что найдут доступ к данным компании явно больше, чем у отдельного взятого человека, который не переходит по странным ссылкам, не увлекается открытием всего и вся на своем устройстве и так далее (но это из разряда идеального пользователя, но мы же делаем прежде всего для себя и беспокоимся о собственной безопасности, вот и надо соответствовать)

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

    Еще в сущности сама защита и не важна, когда ваш компьютер уже заражен каким-нибудь кейлоггером или чем-нибудь посложнее, тогда будь у вас хоть TOTP + аппаратный ключ + 800-значный пароль, все пароли будут просто уводить в момент копирования. Поэтому тут простая формула: каждый пароль уникален, везде ставить двухфакторную аутентификацию. Как минимум даже уведя пароль из базы, человек не сможет получить доступ к аккаунту, даже если он будет знать почту и пароль, потому что письмо придет или на телефон, или на почту, а восстановить почту он не сможет по причине той же двухфакторки или чего-нибудь по типу контрольных вопросов или верификации.