Хеширование паролей в Python: инструкция для новичков
За полчаса вы научитесь правильно хешировать пароли в Python через bcrypt или Argon2 и перестанете хранить их в открытом виде или под MD5. Разберём типичные ошибки, на которых горят новички.

К концу этой инструкции у вас будет рабочий код, который безопасно превращает пароль пользователя в хеш, а потом проверяет введённый пароль против сохранённого значения. На это уйдёт около 30 минут: установить библиотеку, написать две функции, прогнать проверку. Дополнительно разберём ошибки, из-за которых новички хранят пароли так, что их вскрывают за минуты.
Речь именно про пароли, а не про хеши в общем смысле. Хеш от файла для проверки целостности (SHA-256) и хеш пароля — это разные задачи. Для файла нужна скорость, для пароля — наоборот, медленность. Об этом ниже.
Что нужно иметь до начала
- Python 3.8 или новее (проверьте:
python --version). - Установленный
pipи возможность ставить пакеты (venvжелательно, но не обязателен для этой инструкции). - Понимание, что вы работаете с чужими паролями и отвечаете за их сохранность.
Устанавливать сторонние библиотеки будем через pip. Модуль hashlib входит в стандартную библиотеку — его ставить не нужно, но для паролей мы его использовать не будем (почему — в разделе про ошибки).
Шаг 1. Выберите инструмент
Для паролей есть два общепринятых варианта: bcrypt и Argon2. Argon2 победил Password Hashing Competition в 2015 году и считается более современным; bcrypt старше, но проверен временем и проще в установке. Возьмите один из них. В этой инструкции показаны оба — выбирайте по таблице ниже.
| Параметр | bcrypt | Argon2 (argon2-cffi) |
|---|---|---|
| Пакет для pip | bcrypt | argon2-cffi |
| Соль | генерируется автоматически | генерируется автоматически |
| Настройка стойкости | параметр rounds (по умолчанию 12) | time_cost, memory_cost, parallelism |
| Порог входа | ниже | чуть выше из-за числа параметров |
Если сомневаетесь — начните с bcrypt. Перейти на Argon2 позже несложно.
Шаг 2. Установите библиотеку
Для bcrypt:
pip install bcryptИли для Argon2:
pip install argon2-cffiПроверка: команда завершилась строкой вида Successfully installed bcrypt-.... Если видите ошибку компиляции — обычно помогает обновление pip: pip install --upgrade pip.
Шаг 3. Захешируйте пароль (bcrypt)
Вот полный рабочий фрагмент. Соль bcrypt генерирует сам и кладёт её внутрь итогового хеша — отдельно хранить соль не нужно.
import bcrypt
# Пароль от пользователя приходит как строка — кодируем в байты
raw_password = "correct horse battery staple"
password_bytes = raw_password.encode("utf-8")
# gensalt() создаёт соль; rounds=12 — разумное значение по умолчанию
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password_bytes, salt)
# hashed — это байты, которые вы сохраняете в БД
print(hashed)
# Пример вывода: b'$2b$12$...'
Проверка: в консоли появилась строка, начинающаяся с b'$2b$12$'. Число 12 — это те самые rounds, они хранятся прямо в хеше.
Шаг 4. Проверьте введённый пароль (bcrypt)
При логине вы не расшифровываете хеш — расшифровать его нельзя. Вы хешируете введённый пароль тем же алгоритмом и сравниваете. checkpw делает это за вас, включая извлечение соли из сохранённого хеша.
import bcrypt
# hashed вы прочитали из БД (здесь — из шага 3)
attempt = "correct horse battery staple".encode("utf-8")
if bcrypt.checkpw(attempt, hashed):
print("Пароль верный")
else:
print("Пароль неверный")
Проверка: с правильным паролем печатается «Пароль верный», с любым другим — «Пароль неверный».
Шаг 5. Тот же результат на Argon2
Если выбрали Argon2, работает так. Библиотека сама кодирует строку и хранит все параметры внутри хеша.
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher() # параметры по умолчанию
# Хеширование — строку кодировать вручную не нужно
hashed = ph.hash("correct horse battery staple")
print(hashed) # строка вида $argon2id$v=19$...
# Проверка бросает исключение при несовпадении
try:
ph.verify(hashed, "correct horse battery staple")
print("Пароль верный")
except VerifyMismatchError:
print("Пароль неверный")
Проверка: хеш начинается с $argon2id$. Обратите внимание на разницу с bcrypt: verify при несовпадении не возвращает False, а бросает исключение — его нужно ловить.
Пароль нельзя «расшифровать» — только захешировать заново и сравнить. Если библиотека предлагает вам «получить исходный пароль из хеша», это не хеш паролей, а обратимое шифрование, и для этой задачи оно не подходит.
Частые ошибки новичков
MD5 и SHA-256 для паролей
Самая частая ошибка — взять hashlib.md5() или hashlib.sha256(). Эти алгоритмы быстрые, а для паролей нужна медленность: на видеокарте перебор SHA-256 идёт миллиардами вариантов в секунду. bcrypt и Argon2 специально замедлены и требуют памяти, чтобы перебор стоил дорого. SHA-256 оставьте для контрольных сумм файлов, не для паролей.
Своя соль вместо встроенной
Новички часто конкатенируют пароль с солью вручную и суют в SHA. bcrypt и Argon2 генерируют криптостойкую соль сами и упаковывают её в результат. Не придумывайте собственную схему с солью — вы почти наверняка сделаете её слабее.
Сравнение хешей через ==
Если вы всё же сравниваете хеши сами (например, HMAC-подписи), обычный == уязвим к атакам по времени. Используйте hmac.compare_digest() из стандартной библиотеки. Для паролей это не нужно: checkpw и verify сравнивают безопасно внутри себя.
Смешение str и bytes
bcrypt работает с байтами. Если передать строку напрямую, получите TypeError: Strings must be encoded before hashing. Всегда вызывайте .encode("utf-8") перед hashpw и checkpw. У Argon2 через PasswordHasher этой проблемы нет — он принимает строку.
Если не получилось
TypeError: Strings must be encoded before hashing— вы передали строку в bcrypt без.encode("utf-8"). Закодируйте пароль в байты.ModuleNotFoundError: No module named 'bcrypt'— пакет не установлен или установлен в другое окружение. Проверьте, чтоpipиpythonиз одного venv.- Ошибка компиляции при установке argon2-cffi — часто не хватает инструментов сборки. Обновите pip; на большинстве систем ставится готовый wheel без компиляции.
checkpwвсегда возвращает False на верном пароле — обычно хеш из БД прочитан какstr, а нуженbytes. Закодируйте сохранённый хеш обратно или храните его как bytes/BYTEA.VerifyMismatchErrorвместо False у Argon2 — это не баг:verifyбросает исключение при несовпадении. Оборачивайте вызов в try/except.
Что делать дальше
Когда базовое хеширование работает, посмотрите на две вещи. Первое: argon2-cffi умеет проверять, не устарели ли параметры хеша, — метод check_needs_rehash; при успешном логине можно перехешировать пароль с новыми настройками. Второе: подберите стойкость под своё железо. Для bcrypt увеличивайте rounds, пока проверка пароля не займёт около 200-300 мс на вашем сервере, — это компромисс между скоростью логина и стоимостью перебора. Точные рекомендации по параметрам Argon2 смотрите в документации argon2-cffi для вашей версии.
Антиблок-аналитик 115-ФЗ: Анализ выписок, поиск рисков блокировки счёта, инструкции по безопасным транзакциям.
Спросить за 41 ₽Источники: argon2-cffi — официальная документация, bcrypt — репозиторий на PyPI
Частые вопросы
Что делать, если у меня Python 3.7 или другая версия библиотеки?
Можно ли использовать hashlib из стандартной библиотеки для паролей?
Нужно ли хранить соль отдельным полем в базе?
Почему checkpw возвращает False, хотя пароль точно правильный?
Насколько медленным должно быть хеширование, чтобы это было безопасно?
Что делать, если параметры старых хешей устарели?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.