Истории успеха наших клиентов — лучшие проекты
Вход/ Регистрация

Как безопасно хранить пароли с помощью PostgreSQL

7770
12 минут чтения
Средний рейтинг статьи: 4.5

PostgreSQL — это бесплатная объектно-реляционная база данных с открытым исходным кодом.

Объектно-реляционные БД отличаются от просто реляционных. Поэтому PostgreSQL — не то же самое.

Данные все также хранятся в таблицах, столбцы которых связаны друг с другом. Однако PostgreSQL работает согласная стандартам ACID (Atomicity, Consistency, Isolation and Durability), которые гарантируют достоверность данных за счет консистентности и атомарности операций внутри таблиц, — изменения вносятся последовательно, что позволяет отлавливать сбои сразу по ходу записи значений.

PostgreSQL поддерживает мультиверсионность (Multiversion concurrency control, MVCC) — особенность БД, за счет которой создаются копии записей во время изменения, защищающие от потерей и конфликтов одновременного чтения или записи.

Система индексации PostgreSQL устроена сложнее и работает быстрее — используются деревья и разные типы индексации: частичная, хеш, выражения.

Синтаксис PostgreSQL аналогичен синтаксису MySQL, однако первая поддерживает дополнительные подзапросы, такие как «LIMIT» или «ALL».

К тому же PostgreSQL совместима с большим количество языков программирования. Самые основные:

  • C/C++
  • Delphi
  • Erlang
  • Go
  • Java
  • Javascript
  • JSON (native since version 9.2)
  • Lisp
  • .NET
  • Python
  • R
  • Tcl

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

В этой статье мы разберемся, как правильно хранить пароли в базе данных (записывать и читать) с помощью PostgreSQL, соблюдая все меры безопасности.

DBaaS

Запустите свою базу данных в облаке и
оптимизируйте процессы DevOps и CI/CD.

Зачем защищать учетные данные

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

Вот короткие причины почему.

Взлом разработчика

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

Легкомысленность пользователей

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

Репутация и доверие

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

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

Сперва отметим, что в случае с паролями выполняется не шифрование в прямом виде, а скорее хэширование.

Важно понимать, что если что-то шифруется, то это всегда можно расшифровать. Зашифрованная информация является той же самой информацией, но в другом представлении.

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

Главное, что из хэша нельзя получить (теоретически можно, но на практике пока что еще нет) исходные данные. Короче, хэширование — это одностороннее вычисление.

Вот еще несколько неочевидных минусов шифрования, благодаря которым эволюционно прижилось именно хэширование:

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

Как выглядит хэш? По сути, это строка случайного вида — набор символов, лишенный смысла. Алгоритм, который генерирует такую строку, называют хэш-функцией.

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

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

Хэширование непосредственно в PostgreSQL

Встроенное расширение pgcrypto_

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

    

Эта команда загрузит доступное расширение в вашу текущую базу данных — в том случае, если его в ней еще нет. По факту будет запущен сценарий расширения, добавляющий новые SQL-объекты — функции, типы данных, операторы и методы индексации.

Добавление соли через gen_salt()

Чтобы сделать хэш еще надежнее, во время операции хэширования добавляется так называемая соль.

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

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

Решение тривиально. Использовать в качестве входных данных при хэшировании не только пароль, но и некий дополнительный текст — соль.

Соль представляет собой псевдослучайную строку, которая и обеспечивает уникальность хэша на выходе.

В PostgreSQL для этого есть специальная функция — gen_salt(), в качестве аргумента которой передается тип криптографического алгоритма:

  • md5 (MD5)
  • des (DES)
  • xdes (Extended DES)
  • bf (Blowfish)

Например, вот так можно получить соль, используя довольно популярные MD5:

    

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

Итак, с солью мы определились. Теперь рассмотри варианты самого хэширования.

Хэширование пароля с помощью функции crypt()

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

Сама генерация выполняется с помощью встроенной функции crypt(). У нее есть 2 аргумента:

  • строка пароля
  • строка соли

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

    

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

    

Проверка пароля с ранее созданным хэшем

Что интересно, проверка пароля выполняется той же самой функцией хэширования. Отличаются только аргументы.

Например, чтобы проверить пароль «password» на соответствие его же хэшу, выполняется команда:

    

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

Однако, если пароль неверный, хэш будет отличаться:

    

Еще раз напомним. Вызов crypt с паролем password и хэшем этого пароля hash выведет на выходе хэш hash пароля password. В любом другом случае вывод будет отличаться.

Как использовать хэширование PostgreSQL на практике?

  1. Создание таблицы для паролей

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

Поэтому мы создадим таблицу account с тремя столбцами: идентификатор, логин и хэш пароля:

    

Теперь заполним созданную таблицу импровизированными учетными данными:

    

Примерно так можно сохранить пароль в базе данных PostgreSQL. Дополнительно указывается логин — обычно это почта или номер телефона.

  1. Обновление пароля в таблице

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

    
  1. Проверка введенного пароля с ранее сохраненным

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

    

Если password_match окажется равно t (true), то пароли совпадают. Если f (false) — пароли разные.

Кстати, у функции gen_salt есть дополнительный аргумент — количество итераций. Работает он только с алгоритмами xdes и bf:

  • Число итераций xdes может быть нечетным числом от 1 до 16777215. По умолчанию — 725
  • Число итераций bf может быть целым числом от 4 до 31. По умолчанию — 6

Например, вот так можно задать количество итераций для Extended DES:

    

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

Хэширование на стороне клиентского или серверного приложения

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

Возможен и другой вариант — хэширования пароля на уровне серверного (не клиентского) приложения, а не самой БД. В этом случае уже готовый хэш помещается в таблицу база данных, как и любое другое значение.

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

Одни из таких модулей — пакет bcrypt на основе алгоритма Blowfish. От языка к языку интерфейс может отличаться, но функционал один и тот же.

Вот простой пример использования bcrypt в Python:

    

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

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

И самое главное — не изобретайте велосипед. Как встроенные функции (расширения) PostgreSQL, так и проверенные временем внешние библиотечные решения — все они написаны опытными людьми, выполнившими множество итераций по исправлению ошибок и уязвимостей.

Нет никакого смысла создавать собственные криптографические «нагромождения», наивно полагая, что это окажется более лучшим решением. Скорее всего это приведет ко множеству внутренних проблем и увеличит вероятность взломов.

Подготовили для вас выгодные тарифы на DBaaS

Cloud DB 1/1/8

447 ₽/мес

Процессор
1 x 3.3 ГГц
Память
1 ГБ
Диск NVMe
8 ГБ
Приватный IP
Есть
Резервные копии
Есть
Cloud DB 1/2/20

711 ₽/мес

Процессор
1 x 3.3 ГГц
Память
2 ГБ
Диск NVMe
20 ГБ
Приватный IP
Есть
Резервные копии
Есть
Таблица тарифов
Сравнение тарифов
Cloud DB 1/1/8
496
Cloud DB 1/2/20
790
Cloud DB 2/2/30
1160
Cloud DB 2/4/40
1580
Cloud DB 4/8/80
3160
Cloud DB 4/12/120
4240
Cloud DB 6/12/180
5460
Cloud DB 8/16/220
7040
Процессор1 x 3.3 ГГц1 x 3.3 ГГц2 x 3.3 ГГц2 x 3.3 ГГц4 x 3.3 ГГц4 x 3.3 ГГц6 x 3.3 ГГц8 x 3.3 ГГц
Память1 ГБ2 ГБ2 ГБ4 ГБ8 ГБ12 ГБ12 ГБ16 ГБ
Диск NVMe8 ГБ20 ГБ30 ГБ40 ГБ80 ГБ120 ГБ180 ГБ220 ГБ
Приватный IPЕстьЕстьЕстьЕстьЕстьЕстьЕстьЕсть
Резервные копииЕстьЕстьЕстьЕстьЕстьЕстьЕстьЕсть

Заключение

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

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

Например, существуют протоколы (стандарты) OpenID и OAuth 2.0. Последний используется в Google API для авторизации пользователей, поэтому любой может подключить аутентификацию через Google в свое приложение или онлайн-сервис.

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

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

7770
12 минут чтения
Средний рейтинг статьи: 4.5
Хотите внести свой вклад?
Участвуйте в нашей контент-программе за
вознаграждение или запросите нужную вам инструкцию
img-server