Пароль — это секрет, который пользователь предъявляет системе, чтобы подтвердить, что он тот, за кого себя выдает. Это фактор аутентификации из категории «то, что я знаю». Дальше разберем два разных вопроса, которые часто путают: как сделать пароль стойким к подбору (сторона пользователя) и как его безопасно хранить на сервере через хеширование (сторона разработчика), а также 2FA, менеджеры паролей и типичные ошибки.
Содержание
Три близких понятия: не путать кодирование, шифрование и хеширование
Прежде чем говорить о хранении паролей, разведем три операции, которые постоянно смешивают. От выбора между ними зависит, защищен пароль или нет.
| Операция | Обратима? | Нужен ключ? | Для чего | Пример |
|---|---|---|---|---|
| Кодирование | да, всегда | нет | смена формата представления | Base64, URL-encode |
| Шифрование | да, при наличии ключа | да | конфиденциальность данных | AES, RSA-OAEP |
| Хеширование | нет (одностороннее) | нет | проверка без хранения оригинала | Argon2, bcrypt |
Ключевой вывод: кодирование (например, Base64) — это не защита, его разворачивают в один шаг. Шифрование обратимо тем, у кого есть ключ. А пароли на сервере хранят через хеширование — одностороннее преобразование, из результата которого исходный пароль нельзя восстановить напрямую. При входе система хеширует введенный пароль заново и сравнивает хеши, а не сами пароли.
Как правильно хранить пароли на сервере
Правило простое: сервер не должен хранить пароль в открытом виде и не должен уметь его восстановить. Хранят только хеш, полученный специализированной «медленной» функцией с солью.
Соль (salt) — это случайное значение, свое для каждого пароля. Оно добавляется к паролю перед хешированием и хранится рядом с хешем (соль не секрет). Соль решает две задачи: одинаковые пароли двух пользователей дают разные хеши, и заранее посчитанные таблицы хешей (радужные таблицы) становятся бесполезны.
Почему нельзя брать обычные быстрые хеши вроде MD5 или одиночного SHA-256: они создавались для скорости. На видеокарте перебираются миллиарды вариантов в секунду, поэтому по хешу быстро подбирают короткий пароль. Для паролей нужны намеренно медленные и настраиваемые функции.
| Функция | Тип | Годится для паролей | Замечание |
|---|---|---|---|
| MD5, SHA-1 | быстрый хеш | нет | рассчитаны на скорость — дешевый массовый перебор; у MD5/SHA-1 вдобавок есть криптослабости |
| SHA-256 сам по себе | быстрый хеш | нет | слишком быстрый, перебор на GPU |
| bcrypt | адаптивный, медленный | да, для legacy | для новых систем предпочтителен Argon2id; настраиваемая «цена», предел входа 72 байта |
| scrypt | требователен к памяти | да | память затрудняет атаку на GPU/ASIC; запасной вариант, если Argon2id недоступен |
| Argon2id | требователен к памяти и времени | да, для новых систем | выбор по умолчанию по OWASP; вариант семейства Argon2 — победителя конкурса PHC (2015) |
Антипример и как исправить
Так делать нельзя — быстрый хеш без соли подбирается перебором мгновенно:
import hashlib
# ПЛОХО: MD5 без соли, быстрый хеш
bad = hashlib.md5(b"123456").hexdigest()
print(bad) # e10adc3949ba59abbe56e057f20f883e
Этот хеш известного пароля есть в любой готовой таблице, обращение занимает доли секунды. Для новых систем OWASP рекомендует по умолчанию Argon2id (пакет argon2-cffi): он требователен и к времени, и к памяти, что затрудняет перебор на GPU и ASIC. Начинайте с него:
from argon2 import PasswordHasher
ph = PasswordHasher() # параметры по умолчанию - Argon2id
hashed = ph.hash("correct horse battery staple")
print(hashed)
# $argon2id$v=19$m=65536,t=3,p=4$... соль внутри строки, случайна
print(ph.verify(hashed, "correct horse battery staple")) # True
# ph.verify(hashed, "wrong") -> выбросит VerifyMismatchError
Соль генерируется автоматически и встраивается в строку хеша, а verify сам достает ее для сравнения. Если Argon2id недоступен, OWASP допускает scrypt; bcrypt берут прежде всего для совместимости с уже существующими системами:
bcrypt — для наследуемых систем. Рабочий и распространенный вариант, но с пределом входа в 72 байта: все, что длиннее (в том числе длинные Unicode-пароли), молча обрезается. Если поддерживаете такие пароли — проверьте поведение конкретной реализации и настройте
cost(rounds) по замерам на своем железе.
import bcrypt
password = b"correct horse battery staple"
# rounds (cost) задает вычислительную цену; соль случайна и уже внутри хеша
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
print(hashed.decode())
# $2b$12$.... у вас будет другая строка - соль случайна
print(bcrypt.checkpw(password, hashed)) # True
print(bcrypt.checkpw(b"wrong", hashed)) # False
Вывод обеих функций недетерминирован: из-за случайной соли строка хеша каждый раз разная, и это правильно. Важная граница: конкретные параметры (объем памяти, число проходов, rounds) подбирают под свое железо так, чтобы одна проверка занимала десятки-сотни миллисекунд. Это компромисс между удобством пользователя и стоимостью атаки, единственно верного числа тут нет.
Требования к паролям и защита входа на стороне приложения
Медленный хеш защищает базу ПРИ утечке — офлайн-перебор становится дорогим. Но он не ограничивает попытки входа через форму: это отдельная угроза (онлайн-подбор), и закрывают ее иначе.
Политика паролей для сервиса (по NIST SP 800-63B):
- минимальная длина — от 15 символов для однофакторного пароля (в составе MFA допустимо от 8);
- максимум не меньше 64 символов, разрешать пробелы и Unicode;
- не навязывать обязательные наборы символов (регистр, цифры, спецсимволы) — это лишь снижает энтропию предсказуемым образом;
- проверять новый пароль по блок-листу распространенных и уже утекших значений.
Защита от онлайн-подбора (отдельно от хранения хеша):
- ограничение частоты попыток (rate limiting), адаптивная задержка или аккуратная блокировка;
- журналирование входов и обнаружение credential stuffing;
- MFA как страховка на случай кражи самого пароля.
Что делает пароль стойким
Стойкость к перебору определяется не «сложностью символов на вид», а числом равновероятных вариантов, которые атакующему пришлось бы проверить. Для случайного пароля это оценивают через энтропию: чем больше алфавит и длина, тем больше вариантов.
Отсюда практический вывод, который стоит пометить как упрощение: при случайной генерации длина дает больше стойкости, чем экзотические символы. Оговорка — это верно именно для случайных паролей; осмысленная фраза из словарных слов подбирается по словарю быстрее, чем говорит ее длина.
Работающий прием — длинная парольная фраза из несвязанных слов или пароль, сгенерированный случайно. Не придумывайте случайность в голове, берите ее из криптостойкого источника:
import secrets
import string
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
password = "".join(secrets.choice(alphabet) for _ in range(16))
print(password) # напр. 'T7v!qLm2$xR9wZ4a' - у вас будет другой
Модуль secrets предназначен для секретов (в отличие от random, который для этого не годится). Длину 16 символов здесь стоит считать разумным минимумом для важных аккаунтов, а не жестким стандартом.
Частые ошибки
- Один пароль на много сервисов. После любой утечки его подставляют на другие сайты (атака credential stuffing).
- Личные данные в пароле: даты, имена, клички, город — все, что можно узнать из соцсетей.
- Короткие и словарные пароли, «12345», «qwerty», «password».
- Хранение паролей на сервере в открытом виде или под быстрым хешем без соли — ошибка уже на стороне разработчика.
Менеджеры паролей и двухфакторная аутентификация
Держать в голове десятки длинных уникальных паролей невозможно — и не нужно. Эту задачу решает менеджер паролей: он генерирует случайные пароли, хранит их в зашифрованном хранилище и подставляет по запросу. Пользователь помнит один мастер-пароль. Примеры: KeePassXC, Bitwarden, 1Password. Хранилище защищено шифрованием, ключ выводится из мастер-пароля, поэтому его стойкость критична.
Двухфакторная аутентификация (2FA) добавляет второй фактор к паролю, чтобы одной кражи пароля было недостаточно. Факторы бывают трех родов: знание (пароль), владение (телефон, ключ) и биометрия (отпечаток, лицо). Настоящая двухфакторность — это два разных рода, а не два пароля.
| Второй фактор | Защищает от кражи пароля | Устойчив к фишингу | Замечание |
|---|---|---|---|
| SMS-код | да | нет | уязвим к перехвату и подмене SIM, но лучше, чем ничего |
| TOTP-приложение | да | нет | код можно выманить на фишинговом сайте и сразу переиспользовать; работает офлайн (Google Authenticator, Aegis) |
| Аппаратный ключ FIDO2 | да | да | WebAuthn привязывает подтверждение к домену, выманить код нельзя |
Отдельно стоит упомянуть тренд на беспарольный вход — passkeys на базе стандарта WebAuthn/FIDO2. Вместо пароля используется пара ключей: закрытый остается на устройстве, сайту уходит только подтверждение. Это убирает и подбор, и фишинг пароля как класс, и в 2026 году passkeys поддерживают крупные платформы и сервисы.
Выводы
- Пароль на стороне пользователя и хранение пароля на сервере — две разные задачи; их решают по-разному.
- Кодирование, шифрование и хеширование — разные операции; пароли хранят через одностороннее хеширование, а не шифрование.
- На сервере пароль хранят только как хеш с солью, полученный медленной функцией: для новых систем по умолчанию Argon2id (затем scrypt), а bcrypt — для совместимости с наследуемыми; не MD5/SHA-1 и не одиночный SHA-256.
- Медленный хеш защищает базу при утечке (офлайн-перебор), но не вход: от онлайн-подбора нужны ограничение попыток, мониторинг и MFA, а политику длины/блок-лист задают по NIST.
- Стойкость случайного пароля определяет прежде всего длина; уникальность на каждый сервис важнее «сложности» символов.
- Менеджер паролей снимает нагрузку с памяти, 2FA страхует от кражи пароля (но только FIDO2 устойчив к фишингу), а passkeys постепенно убирают пароль как таковой.
Где применяется / связь с практикой
Освойте тему на практике
Хранение паролей, соль, выбор функции хеширования, настройка 2FA и защита от перебора — это повседневные задачи инженера по безопасности и любого бэкенд-разработчика, который проектирует аутентификацию. Разобрать их системно, с моделью угроз и практикой, помогает курс Информационная безопасность. Посмотреть формат и глубину занятий без оплаты можно на открытых уроках — там разбирают в том числе прикладные темы по защите данных.
Смежные темы: Все об асимметричном шифровании, Все о кодировках в компьютерах.
FAQ
Можно ли расшифровать пароль из его хеша?
Напрямую нет: хеширование одностороннее. Слабый пароль под быстрым хешем восстанавливают перебором и по готовым таблицам, но это не расшифровка, а подбор совпадения — соль и медленная функция делают его нерентабельным.
Нужно ли периодически менять пароль без причины?
Современные рекомендации (в том числе NIST) от обязательной ротации по расписанию отошли: она подталкивает к предсказуемым вариациям. Менять стоит при подозрении на утечку; проверить свою почту можно в сервисах контроля утечек.
Менеджер паролей — это не опасно, ведь все пароли в одном месте?
Риск концентрации есть, но на практике он ниже, чем повторное использование слабых паролей. Хранилище зашифровано, а мастер-пароль стоит сделать длинным и закрыть двухфакторной аутентификацией.



