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

Что вы получите в конце
К концу этой инструкции у вас будет рабочий код на Python 3.12, который: превращает пароль пользователя в хеш для хранения в базе, проверяет введённый пароль при входе и не даёт восстановить исходный пароль из хеша. Разберём два алгоритма — bcrypt и Argon2, оба считаются пригодными для паролей на 2024 год. Займёт всё вместе минут двадцать-тридцать, если Python уже установлен.
Отдельно проговорю границу: речь про хеширование паролей для аутентификации. Хеши для проверки целостности файлов (SHA-256 от архива) или подписи данных — другая задача с другими требованиями, здесь её не трогаем.
Что нужно иметь до начала
- Python 3.8 или новее. Проверьте командой
python --versionв терминале — должна вывестись строка видаPython 3.12.x. - Доступ к установке пакетов через
pip. Если работаете на общем сервере — права на запись в виртуальное окружение. - Понимание, что хеш нужно хранить в базе как строку. Тип поля — обычный
varcharилиtext, длины 60 символов для bcrypt хватает, для Argon2 беритеtext.
Почему не MD5, SHA-1 и SHA-256
Эти алгоритмы быстрые — и в этом проблема. Видеокарта перебирает миллиарды SHA-256-хешей в секунду, так что база утёкших MD5-паролей ломается словарём за минуты. Для паролей нужен намеренно медленный алгоритм с солью*. bcrypt и Argon2 такие.
* Соль — случайная строка, которую добавляют к паролю перед хешированием, чтобы одинаковые пароли давали разные хеши. И bcrypt, и Argon2 генерируют соль сами и хранят её внутри итогового хеша — отдельно её сохранять не нужно.
Вариант 1. bcrypt — самый простой старт
bcrypt проверен временем, есть почти везде и не требует настройки. Установите пакет:
pip install bcryptПроверка: команда завершится строкой вида Successfully installed bcrypt-4.x.x.
Шаг 1. Создать хеш при регистрации
import bcrypt
def hash_password(plain_password: str) -> str:
# bcrypt работает с байтами, поэтому кодируем пароль в UTF-8
password_bytes = plain_password.encode("utf-8")
# gensalt() создаёт случайную соль; rounds=12 — стоимость подбора
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password_bytes, salt)
# в базу удобнее хранить строкой
return hashed.decode("utf-8")
stored_hash = hash_password("correct horse battery staple")
print(stored_hash)Проверка: в консоль выведется строка, начинающаяся с $2b$12$ и длиной около 60 символов. Запустите функцию с тем же паролем дважды — хеши будут разными, потому что соль каждый раз новая. Это нормально.
Шаг 2. Проверить пароль при входе
def verify_password(plain_password: str, stored_hash: str) -> bool:
password_bytes = plain_password.encode("utf-8")
hash_bytes = stored_hash.encode("utf-8")
# checkpw сам достаёт соль из хеша и сравнивает
return bcrypt.checkpw(password_bytes, hash_bytes)
print(verify_password("correct horse battery staple", stored_hash)) # True
print(verify_password("wrong password", stored_hash)) # FalseПроверка: первый вызов вернёт True, второй False. Обратите внимание — вы не пишете сравнение hash1 == hash2 вручную. checkpw делает это устойчиво к тайминг-атакам сам.
Не сравнивайте хеши обычным оператором ==. Время сравнения может выдать, сколько символов совпало. Функции проверки в bcrypt и Argon2 используют сравнение с постоянным временем — доверьте эту работу им.Ограничение bcrypt, о которое спотыкаются
bcrypt обрезает пароль до 72 байт. Всё, что длиннее, молча игнорируется — два пароля с одинаковыми первыми 72 байтами дадут совпадение при проверке. На практике мешает редко, но если разрешаете очень длинные пароли или парольные фразы, это повод посмотреть в сторону Argon2 либо пропустить пароль через SHA-256 перед bcrypt (тогда на вход всегда идут 32 байта фиксированной длины).
Вариант 2. Argon2 — современный выбор
Argon2 победил конкурс Password Hashing Competition в 2015 году и рекомендуется как алгоритм по умолчанию для новых проектов. Он сложнее для перебора на видеокартах за счёт настраиваемого расхода памяти. Установите:
pip install argon2-cffiПроверка: строка вида Successfully installed argon2-cffi-23.x.x.
Шаг 1 и 2. Хеш и проверка
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
# PasswordHasher с параметрами по умолчанию подходит большинству проектов
ph = PasswordHasher()
# Создание хеша при регистрации
stored_hash = ph.hash("correct horse battery staple")
print(stored_hash) # строка вида $argon2id$v=19$m=65536,t=3,p=4$...
# Проверка при входе
def verify(plain_password: str, stored_hash: str) -> bool:
try:
ph.verify(stored_hash, plain_password)
return True
except VerifyMismatchError:
return False
print(verify("correct horse battery staple", stored_hash)) # True
print(verify("wrong password", stored_hash)) # FalseПроверка: хеш начинается с $argon2id$, первый вызов verify даёт True, второй False. Важное отличие от bcrypt: ph.verify при несовпадении бросает исключение, а не возвращает False, — поэтому оборачиваем в try/except.
Бонус Argon2: перехеширование
Со временем вы захотите поднять сложность. Argon2 умеет подсказать, что старый хеш пора обновить:
# после успешной проверки пароля
if ph.check_needs_rehash(stored_hash):
new_hash = ph.hash(plain_password)
# сохранить new_hash в базу вместо старогоЭто единственный момент, когда можно бесшовно усилить хеши: в руках есть открытый пароль сразу после входа.
Что выбрать
| Критерий | bcrypt | Argon2 (argon2id) |
|---|---|---|
| Зрелость | с 1999 года, проверен | с 2015, рекомендуется как дефолт |
| Настройка | только rounds | память, итерации, параллелизм |
| Устойчивость к GPU/ASIC | средняя | выше за счёт расхода памяти |
| Ограничение длины | 72 байта | практически нет |
| Проверка при ошибке | возвращает False | бросает исключение |
Для нового проекта берите Argon2id. Если поддерживаете существующую систему, где всё на bcrypt, — не переписывайте ради переписывания: bcrypt с rounds=12 в 2024 году всё ещё приемлем.
Если не получилось
ModuleNotFoundError: No module named 'bcrypt'— пакет не установлен в активное окружение. Проверьте, чтоpipиpythonиз одного окружения:python -m pip install bcrypt.TypeError: Strings must be encoded before hashingв bcrypt — вы передалиstrвместо байтов. Добавьте.encode("utf-8").ValueError: Invalid saltпри проверке bcrypt — в базе лежит не bcrypt-хеш или строка повреждена (обрезана по длине поля). Проверьте, что поле вмещает 60 символов.argon2.exceptions.InvalidHashError— на входverifyподан хеш не в формате Argon2. Частая причина — перепутаны местами аргументы: сначала идёт хеш, потом пароль.- Проверка всегда возвращает False при верном пароле — почти всегда рассинхрон кодировок. Убедитесь, что и при хешировании, и при проверке пароль кодируется одинаково (UTF-8).
Что делать дальше
Заведите поле под хеш в базе, замените в коде регистрации и входа прямое хранение пароля на функции выше. После этого посмотрите на три вещи: включите ограничение частоты попыток входа (rate limiting), чтобы медленный хеш не превратился в вектор перегрузки сервера; при массовой миграции старых MD5/SHA-паролей — перехешируйте их в момент первого входа пользователя; и вынесите параметры сложности (rounds для bcrypt, память для Argon2) в конфиг, чтобы поднять их через год без правки кода. Инвалидация сессий после смены пароля — отдельная тема, здесь её не разбираем.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: argon2-cffi — официальная документация, bcrypt — репозиторий на PyPI
Частые вопросы
А если у меня Python 3.8, а не 3.12 — код сработает?
Нужно ли хранить соль отдельным полем в базе?
Можно ли использовать hashlib и SHA-256 для паролей, раз он в стандартной библиотеке?
Что будет, если база с хешами утечёт?
Как выбрать rounds для bcrypt или параметры памяти для Argon2?
Проверка проходит на локали, но падает на проде — в чём подвох?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.