Хеширование против шифрования против подписи
Хеширование, шифрование и подпись — три разных примитива, которые инженеры постоянно путают. Каждый гарантирует своё — а выбор не того примитива и есть способ, которым утекают пароли, подделываются сессии, а «оно зашифровано» оказывается пустым словом.
Джуниор из твоей команды выкатывает «безопасное» хранилище паролей: AES-256 поверх каждого пароля, ключ в конфиге. Ревью одобряет — AES-256, звучит надёжно. Через полгода утекает бэкап, вместе с ним утекает ключ, и каждый пароль в базе восстанавливается в открытом виде. Баг был не в слабом алгоритме. Это был полностью неверный примитив: пароли нужно хешировать, а не шифровать, именно потому что хеширование — единственная операция, которую нельзя обратить. На той же неделе другой инженер «подписывает» API-нагрузку, прогоняя её через SHA-256, — и не доказывает ничего, ведь этот хеш может пересчитать кто угодно. Три примитива, три разные гарантии, и обращение не к тому из них — один из самых частых способов, которыми в остальном аккуратные системы получают пробой.
К концу этого урока ты сможешь для хеширования, шифрования и подписи назвать ровно то, что каждый из них гарантирует, чего он не гарантирует и какой из них на самом деле нужен конкретной задаче.
Три примитива и единственное свойство, которое их разделяет
Самый быстрый способ держать их в голове — задать каждому один вопрос: можно ли вернуть оригинал?
- Хеширование — это односторонняя функция.
SHA-256("hello")всегда даёт один и тот же 256-битный дайджест, ноunhash()не существует — вход уничтожен. Проверяешь, пересчитав хеш кандидата и сравнив. Его работа — целостность и отпечаток: совпали ли эти байты бит-в-бит? Он ничего не говорит о конфиденциальности (дайджест публичен) и ничего о том, кто его создал. - Шифрование — обратимо при наличии ключа.
encrypt(plaintext, key)даёт шифротекст, которыйdecrypt(ciphertext, key)превращает обратно в открытый текст. Его работа — конфиденциальность: держать содержимое в секрете от всех без ключа. Само по себе классическое шифрование не доказывает, что сообщение не подменили, — поэтому современные системы используют аутентифицированное шифрование (AES-GCM, ChaCha20-Poly1305), привинчивающее тег целостности. - Подпись — асимметричная. Подписываешь приватным ключом, а проверяет кто угодно соответствующим публичным. Её работа — аутентичность (это пришло от держателя приватного ключа) плюс целостность (и не было изменено с тех пор). Она намеренно не скрывает содержимое — подписанный JWT читается кем угодно.
Чего каждый из них не гарантирует — режимы отказа
Пробои берутся из негативного пространства: когда примитиву приписывают свойство, которое он никогда не обещал.
Хеширование — не шифрование. Хеш безопасен для публикации, но не для секрета. «Мы хешируем СНИЛСы» не защищает ничего, если пространство входов мало — атакующий хеширует все возможные значения (их порядка 10⁹) и сопоставляет за секунды. Хеширование скрывает только высокоэнтропийное и неугадываемое. Поэтому же простой хеш неверен для паролей: пароли низкоэнтропийны, так что нужен медленный, посоленный парольный хеш (bcrypt, scrypt, Argon2id), весь смысл которого — быть дорогим в вычислении, побеждая офлайн-перебор. SHA-256(пароль) — это миллиарды попыток в секунду на GPU; Argon2id с правильными параметрами — единицы.
Шифрование — не целостность и не аутентификация. Классический шифротекст в режиме CBC может быть изменён атакующим, который не может его прочитать, — переверни биты в шифротексте, перевернутся биты в расшифрованном открытом тексте (семейства атак padding-oracle и bit-flipping). «Оно зашифровано» не значит «оно неподменено», если ты не используешь AEAD-режим, который аутентифицирует. И шифрование ничего не говорит о том, кто отправил: общий ключ означает, что сообщение мог создать любой, у кого этот ключ есть.
Подпись — не шифрование. Подпись прикреплена к данным, которые остаются в открытом виде. Люди регулярно думают, что подписанный JWT «безопасен», и кладут секреты в его нагрузку — но нагрузка это просто base64, читаемый кем угодно, кто перехватит токен. Подпись защищает от подделки и подмены, но никогда от подслушивания.
▸Почему это работает
Почему подпись асимметрична (приватным подписываешь, публичным проверяешь), если MAC на общем ключе вроде HMAC тоже доказывает целостность? Из-за того, кто что может. С HMAC проверяющий держит тот же секрет, что и подписывающий, — значит, проверяющий тоже мог подделать сообщение; он доказывает целостность лишь между взаимно доверяющими сторонами. Цифровая подпись расщепляет ключ: только держатель приватного ключа может создать валидную подпись, а проверить может весь мир, не получая возможности подделать. Поэтому TLS-сертификаты, релизы ПО и JWT, подписанные RS256/ES256, используют асимметричные подписи — нужно, чтобы третьи стороны проверяли происхождение, не доверяя им силу подделки. HMAC (HS256) уместен только тогда, когда одна сторона и выпускает, и проверяет токен.
Выбор верного примитива под задачу
Зрелый ход — отображать требование на примитив, а не имя алгоритма на ощущение «надёжности». Пройди требование: мне нужно держать это в секрете (конфиденциальность → шифрование), доказать, что это не изменили (целостность → хеш/MAC/подпись), доказать, кто это создал (аутентичность → подпись или MAC), или хранить секрет, который мне нужно лишь проверять, никогда не считывая обратно (односторонне → парольный хеш)?
| Требование | Примитив | Конкретный выбор | Ловушка, которой избежать |
|---|---|---|---|
| Хранить пароль | Медленный парольный хеш | Argon2id (или bcrypt/scrypt) + соль на пользователя | Шифровать его (утечка ключа = открытый текст) или простой SHA-256 (перебор на GPU) |
| Держать данные в секрете на хранении / в передаче | Аутентифицированное шифрование | AES-256-GCM или ChaCha20-Poly1305 | Неаутентифицированный CBC (поддаётся подмене); повтор nonce |
| Проверить, что файл/загрузка целы | Хеш (отпечаток) | Дайджест SHA-256, сравнение за постоянное время | MD5/SHA-1 (коллизии); доверие хешу из того же канала |
| Доказать происхождение третьим сторонам | Асимметричная подпись | Ed25519 / ECDSA (RS256/ES256 для JWT) | Класть секреты в подписанную, но незашифрованную нагрузку |
| Защитить токен от подмены между двумя доверяющими сервисами | MAC (общий ключ) | HMAC-SHA-256 | Считать HMAC проверяемой третьими сторонами подписью |
Реальная система обычно композирует их, а не выбирает один. TLS 1.3 — протокол за каждым https:// — использует все три в одном рукопожатии: асимметричную подпись для аутентификации сертификата сервера, асимметричный обмен ключами для согласования секрета, этот секрет питает аутентифицированное шифрование сессии, а хеширование (HKDF) вплетено в вывод ключей и в транскрипт, доказывающий, что само рукопожатие не было подменено. Понимание, какой примитив делает какую работу внутри этого стека, — разница между настройкой TLS и слепым копированием.
Ты хранишь пароли пользователей для системы входа. Выбери верный подход.
Коллега говорит: «JWT подписан нашим ключом, так что класть API-токен пользователя внутрь нагрузки безопасно». В чём ошибка?
Почему шифрование данных одним AES-256-CBC (без MAC, без AEAD) недостаточно, когда атакующий может изменить шифротекст в передаче?
Сопоставь каждое требование с его примитивом — упорядочь требования от «держать в секрете» до «доказать, кто отправил»:
- 1 Сделать содержимое нечитаемым для всех без ключа → шифрование
- 2 Обнаружить любое изменение скачанного файла → хеш (отпечаток)
- 3 Хранить пароль, который только проверяешь, никогда не считывая → медленный посоленный парольный хеш
- 4 Доказать, что сообщение пришло от конкретной стороны, с проверкой кем угодно → асимметричная подпись
- 01Для хеширования, шифрования и подписи назови, что гарантирует каждый, и единственное свойство, которое их различает.
- 02Почему пароли нужно хешировать медленным посоленным хешем, а не шифровать или прогонять через простой SHA-256, и почему подписанный JWT небезопасен для секретов?
Хеширование, шифрование и подпись — три разных примитива, разделённых одним вопросом: можно ли обратить и каким ключом? Хеширование одностороннее и без ключа — покупает целостность и отпечаток, никогда конфиденциальность или происхождение. Шифрование обратимо ключом — покупает конфиденциальность, но классические режимы не дают целостности, так что тянись за аутентифицированным шифрованием (AES-GCM, ChaCha20-Poly1305). Подпись асимметрична — приватным ключом подписать, публичным проверить — покупает проверяемую третьими сторонами аутентичность плюс целостность, оставляя данные читаемыми. Пробои живут в негативном пространстве: шифровать пароли вместо хеширования (утечка ключа = открытый текст), прогонять низкоэнтропийные данные через простой SHA-256 (перебор на GPU), доверять целостность неаутентифицированному CBC (bit-flipping) или набивать секреты в подписанный, но незашифрованный JWT. Зрелый рефлекс — сначала назвать требование (секретно, цело, атрибутируемо или только-проверять-не-читать), и только потом выбирать примитив. Реальные системы вроде TLS 1.3 композируют все три; понимание, что делает каждый, и отличает настройку криптографии от слепого копирования.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.