Российские платёжные сервисы вроде ЮKassa и CloudPayments подписывают уведомления о платежах (вебхуки) именно через HMAC-SHA256: интернет-магазин при регистрации получает секретный ключ, а каждое входящее уведомление сопровождается подписью, которую нужно пересчитать локально и сравнить с присланной. Если подпись не совпадает — уведомление не от платёжного сервиса, а значит, доверять ему нельзя.
Зачем нужен именно секретный ключ, а не просто хеш
Обычный хеш вроде SHA-256 подтверждает лишь то, что данные не повреждены — но не то, кто их создал. Если бы магазин просто хешировал тело уведомления и сравнивал с присланным хешем, злоумышленник мог бы сам сформировать поддельное уведомление об оплате и приложить корректный хеш. HMAC добавляет секретный ключ, известный только магазину и платёжному сервису: без него правильную подпись не подделать, даже зная алгоритм хеширования.
Почему это не просто "хеш от ключа плюс сообщение"
Наивная склейка hash(key + message) уязвима к атакам удлинения сообщения для некоторых хеш-функций — злоумышленник может дописать данные к сообщению и вычислить новый валидный хеш, даже не зная ключа целиком. HMAC использует специальную конструкцию с двойным хешированием и внутренним/внешним падингом, защищённую от этого класса атак.
Где ещё встречается HMAC
Кроме подписи вебхуков, HMAC используется для аутентификации API-запросов (когда сам ключ не передаётся по сети, только подпись), а также лежит в основе алгоритма TOTP — того самого, что генерирует шестизначные коды в приложениях-аутентификаторах.
Зачем это нужно
- Проверять подлинность вебхуков от платёжных и других сторонних сервисов.
- Подписывать API-запросы без передачи самого секретного ключа по сети.
- Понимать внутреннюю механику TOTP-кодов и токенов сессий.
Сравнение за постоянное время
Сравнивать полученную подпись с ожидаемой обычным === опасно: такой оператор обычно останавливается на первом расхождении, и время сравнения выдаёт злоумышленнику, сколько первых байт подписи он подобрал верно. Надёжный код обработки вебхуков всегда использует функции сравнения за постоянное время — например, hash_equals в PHP, — которые проверяют все байты независимо от того, где встретилось первое несовпадение.