Almaz Mubinov

Безопасное хранение пользовательских паролей на веб-сервисах

1 Дек 2012 г. 

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

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

Хранение паролей открытым текстом

Самое небезопасное хранение пароля, так как в данном случае достаточно получить доступ к базе данных и узнать его. Таким образом XSS атаки, SQL-инъекции могут принести успех сразу, без дальнейшего анализа хэшей и прочих действий.

Хранение пароля в открытом виде

Хранение хэшированных паролей

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

Хранение хэшированного пароля

Хранение соленных хэшей

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

Хранение соленных хэшей

Хранение хэшей на основе индивидуальной соли для каждого пользователя

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

Хранение хэшей на основе индивидуальной соли

Хранение паролей в отдельной общей таблице без привязки к конкретным логинам

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

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

Хранение паролей в отдельной общей таблице

Хранение паролей для разработчиков-параноиков

Еще больше усложнить перебор пароля мы можем увеличивая вычислительную сложность алгоритма. Самый простой способ для этого — использование «вложенных» медленных хэш функций. Однако я предлагаю использовать другой способ. Мы создадим отдельную таблицу со всеми вариантами шестнадцатеричных чисел длиною 6 символов. То есть мы получаем таблицу в которой в одном из полей будут все числа от 0х000000 до 0хffffff. Это более 16 миллионов записей. В другом же поле, для каждого такого числа, мы создадим случайную строку-блок, которую будем использовать для генерации хэша.

Хранение паролей для разработчиков-параноиков

После этого на основе пароля и соли пользователя генерируем hex-строку с числом символов кратным 6. Например, возьмем md5 хэш от пароля (32 символа) и добавим к ним первые 4 символа от хэша соли. Таким образом получили hex-строку из 36 символов. Разобьем строку на 6 блоков по 6 символов в каждом. Мы помним, что генерировали записи в таблице промежуточных данных, и можем найти для каждой hex-строки из 6 символов соответствующий ей блок данных в виде строки.

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

Хранение паролей для разработчиков-параноиков

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

По сложности вычислений задача является довольно быстровыполнимой:

— вычисление первого хэша от пароля,

— поиск шести блоков в таблице из 16 млн записей

— хэш от конкатенации полученных блоков

— поиск хэша в таблице паролей

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

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

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

Для того чтобы попробовать на вкус последний способ, можете скачать исходный код проекта с примером: https://github.com/mubinov/users_passwords . Для его использования необходимо выполнить следующие действия:

— Создать структуру данных в БД (файл dump.sql). Должно быть создано три таблицы: users, block и passwords.

— Настроить соединение с БД в файле index.php.

— Заполнить таблицу blocks необходимыми значениями. Для этого удалите комментарии в файле index.php перед строками:

1
2
generateBlocksTable();
die();

generateBlocksTable(); die();

и выполните его. Внимание, процесс создания более 16 миллионов записей в таблице blocks займет некоторое время. После выполнения, необходимо закомментировать эти 2 строки.

— В файле index.php приведены примеры взаимодействия с моделью хранения паролей, а именно функции createUser, changePass, authUser.

Исходный код с примерами для последнего случая: https://github.com/mubinov/users_passwords


Разработка
Almaz Mubinov © 2010-2023