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

    Команда Simple-Server
    03.06.2026
    12 мин

    Материал подготовлен командой Simple-Server для администраторов VPS и выделенных серверов. Команды и пути проверяйте на тестовой машине перед production.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    CREATE EXTENSION pgcrypto;

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

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

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

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

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

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

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

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

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

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

    SELECT gen_salt('md5');

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

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

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

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

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

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

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

    SELECT crypt('password', gen_salt('md5'));

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

    SELECT crypt('password’, 'nothing');

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

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

    SELECT crypt('password’, хэш);

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

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

    SELECT crypt('another_password’, хэш);

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

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

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

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

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

    CREATE table accounts (identifier SERIAL, login VARCHAR(10), password VARCHAR(100));

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

    INSERT INTO accounts (login, password) VALUES ('login_1', crypt('some_password', gen_salt('md5'))) ;

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

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

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

    UPDATE accounts SET password = crypt('new_password', gen_salt('md5')) WHERE login= 'login_1' ;
    1. Проверка введенного пароля с ранее сохраненным

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

    SELECT (password = crypt(введенный_пароль, password)) AS password_match FROM accounts WHERE login= 'login_1' ;

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

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

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

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

    SELECT crypt('password', gen_salt('xdes', 963));

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

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

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

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

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

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

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

    import bcrypt # создание хэша перед тем, как отправить его в БД def generate_hash(password): # конвертация из строки в набор байтов password_bytes = password.encode("utf-8")       # генерация соли password_salt = bcrypt.gensalt()                # генерация хэша hash_bytes = bcrypt.hashpw(password_bytes, password_salt)    # конвертация байтов обратно в строку hash_str = hash_bytes.decode("utf-8")             return hash_str        # выполняет проверку пароля и хэша, после чего возвращает булевый результат def authenticate(password, hash): # конвертируем все из строки в байты password_bytes = password.encode("utf-8") hash_bytes = hash.encode("utf-8") # соль автоматически (криптографически) «обнаруживается» в хэше, поэтому во время проверки ее не надо указывать отдельно result = bcrypt.checkpw(password_bytes, hash_bytes) return result

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

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

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

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


    Нужен сервер для практики? Арендуйте VPS/VDS в России — root-доступ, NVMe, DDoS-защита и поддержка 24/7.

    VPS для проекта

    VPS с root-доступом, NVMe и поддержкой 24/7 на Simple-Server.

    StarterVDS

    490

    в месяц

    1 ядро

    1 ГБ RAM

    20 ГБ NVMe

    • 1 IPv4
    • KVM
    • Root-доступ
    • Безлимитный трафик
    Заказать VPS
    Рекомендуем

    PerformanceVDS

    1190

    в месяц

    2 ядра

    4 ГБ RAM

    60 ГБ NVMe

    • 1 IPv4
    • KVM
    • Root-доступ
    • Базовая DDoS-защита
    Заказать VPS

    Нужна другая конфигурация или чистый VPS без панели?

    Все тарифы VPS

    Похожие статьи, которые могут быть вам интересны