Генератор HMAC и проверка подписи

Вычисляет HMAC-SHA-1, SHA-256, SHA-384 и SHA-512 из сообщения и ключа и определяет, каким из них получена данная подпись.

Сообщение
HMAC-SHA-1
HMAC-SHA-256
HMAC-SHA-384
HMAC-SHA-512

Введите ключ, чтобы вычислить подписи.

Что делает этот инструмент

HMAC превращает сообщение и общий секрет в короткую подпись. Всякий, у кого есть тот же секрет, может пересчитать её и увидеть, дошло ли сообщение неизменным и пришло ли оно от того, кто секрет тоже знает. Этот инструмент считает все четыре распространённых варианта сразу и по присланной подписи говорит, каким из них она получена.

Именно второе направление обычно и приводит сюда. Вебхук не проходит проверку, и настоящий вопрос не «действительна ли эта подпись», а «что из нескольких правдоподобных вещей я делаю иначе, чем отправитель».

Ключ — это место, где всё ломается

HMAC подписывает байты байтами. Сообщение обычно очевидно — сырое тело запроса, — а ключ почти никогда, потому что секрет приходит строкой, а строка не является байтами, пока вы не решили, как её читать:

a3f2  ->  61 33 66 32   4 bytes   как текст
a3f2  ->  a3 f2         2 bytes   как hex

Оба прочтения законны, оба дают совершенно корректную подпись, и у этих двух подписей нет ничего общего. Никто вас не предупредит: ни ошибки, ни жалобы на длину — только значение, не совпадающее с тем, что вычислил отправитель. Поэтому кодировка ключа здесь видимый выбор, а не догадка: когда подпись не совпадает, это первое, что стоит переключить.

Грубый ориентир: секрет с префиксом вроде whsec_ или строка из букв в обоих регистрах и цифр обычно задуман как текст; строка ровно из 32 или 64 знаков, где встречаются только 0-9 и a-f, как правило шестнадцатеричная; а оканчивающаяся на = почти наверняка Base64. Но сверяйтесь с документацией отправителя, а не с формой, потому что форма — не доказательство.

Сообщение должно быть ровно теми байтами

Вторая половина несовпадения — сообщение. HMAC определён над байтами, поэтому всё, что меняет байты, полностью меняет подпись: частичного зачёта и почти-попадания не бывает.

  • Повторная сериализация JSON. Разобрать тело и собрать его заново может переставить ключи, изменить пробелы или нормализовать числа. Подписывайте и проверяйте то сырое тело, которое получили, а не копию, прошедшую туда и обратно.
  • Перевод строки в конце. Некоторые инструменты добавляют его при сохранении тела в файл; это байт, и он меняет всё.
  • Кодировка символов. Тело с не-ASCII текстом должно читаться в одной и той же кодировке с обеих сторон — на практике UTF-8.
  • Сжатие или промежуточный слой. Если что-то перед вашим обработчиком распаковывает или переписывает тело, проверяйте до этого, а не после.

Многие поставщики к тому же подписывают не одно тело. Stripe подписывает метку времени и тело, соединённые точкой; AWS подписывает каноничный запрос, собранный из метода, пути, заголовков и хеша полезной нагрузки. Если проверка против голого тела не проходит, подписываемая строка, скорее всего, не голое тело — это описано на стороне отправителя и стоит прочтения прежде, чем копать дальше.

Почему SHA-1 предлагается здесь и не предлагается в инструменте хеширования

Инструмент хеширования помечает SHA-1 как сломанный. Здесь HMAC-SHA-1 показан без предупреждения, и это намеренно, а не недосмотр.

Для подписей и сертификатов SHA-1 непригоден, потому что коллизии можно построить: два разных документа можно заставить иметь один и тот же хеш. HMAC от этого свойства не зависит. Его безопасность держится на секретном ключе, а конструкция — хешировать сообщение дважды, подмешивая ключ в оба раза, — устойчива даже тогда, когда базовый хеш слаб к коллизиям. HMAC-SHA-1 остаётся надёжным и именно он используется в OAuth 1.0a и старой схеме подписи AWS, так что инструмент, отказавшийся его считать, был бы просто менее полезным, не становясь безопаснее.

Для всего нового разумный выбор — SHA-256. Более длинные хеши здесь не заметно сильнее: потолок безопасности — ключ, а не длина хеша, — поэтому SHA-384 и SHA-512 стоит брать лишь тогда, когда их требует то, с чем вы обязаны взаимодействовать.

Как безопасно сравнивать подписи

Эта страница сравнивает обычным сравнением строк, и здесь это нормально: ключ у вас в руках, утекать нечему. На сервере, который проверяет входящий запрос, это уже не нормально. Обычное сравнение останавливается на первом различающемся байте, поэтому затраченное время выдаёт, какая часть догадки была верна, и тот, кто может слать много запросов, восстановит действительную подпись побайтово.

Используйте сравнение за постоянное время, которое даёт ваша платформа, — crypto.timingSafeEqual в Node, hmac.compare_digest в Python, hash_equals в PHP — по сырым байтам, а не по шестнадцатеричным строкам. Вдобавок сверяйтесь с подписью, вычисленной вами самими, а не доверяйте алгоритму, названному в запросе, и отклоняйте сообщение, метка времени которого далека от текущей, чтобы старую действительную подпись нельзя было воспроизвести.

HMAC — это не хеш и не подпись

Здесь путают три вещи, и разница важна для того, что потом можно утверждать:

  • Хеш берёт сообщение и выдаёт дайджест. Вычислить его может кто угодно, поэтому он доказывает, что сообщение не изменилось случайно, а не то, что оно пришло от кого-то определённого.
  • HMAC берёт сообщение и общий секрет. У обеих сторон один и тот же ключ, поэтому он доказывает, что отправитель знал секрет. Какая именно сторона отправила, он доказать не может: произвести его могла любая.
  • Подпись использует закрытый ключ, который есть только у отправителя, и открытый, которым может проверить любой. Это и даёт неотказуемость: отправитель не сможет потом от неё отпереться.

Итак, HMAC — правильный инструмент между двумя системами, которые уже разделяют секрет: вебхуки, внутренние API, сессионные токены, — и неправильный, когда нужно доказать третьей стороне, кто что-то отправил. Заметьте также: он аутентифицирует, но не скрывает. Сообщение идёт открытым текстом, и о конфиденциальности HMAC не говорит ничего.

Частые вопросы

Моя подпись не совпадает. Что проверить первым?
Кодировку ключа, затем байты сообщения. Секрет, похожий на шестнадцатеричный, часто и задуман как шестнадцатеричный, а не как текст, и оба варианта дают совершенно разные подписи без всякой ошибки. Затем убедитесь, что подписываете сырое тело ровно в том виде, в каком оно пришло, а не пересобранную копию.
Почему SHA-1 предлагается здесь, если инструмент хеширования называет его сломанным?
Потому что HMAC не опирается на стойкость к коллизиям — то самое свойство, которое SHA-1 утратил. Его безопасность идёт от ключа. HMAC-SHA-1 по-прежнему надёжен и используется в OAuth 1.0a и старой подписи AWS, хотя для нового правильное значение по умолчанию — SHA-256.
Длиннее хеш — безопаснее?
Не сколько-нибудь заметно. Стойкость HMAC ограничена секретом, а не длиной хеша, так что SHA-512 не вчетверо лучше SHA-256. Берите тот, которого ждёт другая сторона.
Чем HMAC отличается от цифровой подписи?
HMAC использует секрет, известный обеим сторонам, поэтому доказывает, что отправитель знал секрет, но не то, какая сторона отправила. Цифровая подпись использует закрытый ключ, который есть только у отправителя, поэтому её может проверить третья сторона, а отправитель не сможет отпереться. Если нужно последнее, HMAC — неподходящий инструмент.
Сравнивать ли подписи через ===?
На сервере — нет. Обычное сравнение возвращается, как только байты разошлись, и это время выдаёт, какая часть угаданной подписи была верной. Используйте сравнение за постоянное время вашей платформы по сырым байтам. На этой странице это неважно, потому что ключ у вас и так есть.
Отправляется ли мой ключ куда-нибудь?
Нет. Всё вычисляется в вашем браузере через Web Crypto API; сообщение и ключ никогда не покидают ваше устройство.