Генератор HMAC
Вычисляет HMAC-SHA-1, SHA-256, SHA-384 и SHA-512 из сообщения и ключа и определяет, каким из них получена данная подпись.
Введите ключ, чтобы вычислить подписи.
Что делает этот инструмент
HMAC превращает сообщение и общий секрет в короткую подпись. Всякий, у кого есть тот же секрет, может пересчитать её и увидеть, дошло ли сообщение неизменным и пришло ли оно от того, кто секрет тоже знает. Этот инструмент считает все четыре распространённых варианта сразу и по присланной подписи говорит, каким из них она получена.
Именно второе направление обычно и приводит сюда. Вебхук не проходит проверку, и настоящий вопрос не «действительна ли эта подпись», а «что из нескольких правдоподобных вещей я делаю иначе, чем отправитель». Префикс вида sha256= перед вставленной подписью распознаётся и отбрасывается, поэтому значение заголовка можно вставить ровно в том виде, в каком оно пришло.
Ключ — это место, где всё ломается
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 не говорит ничего.
Проверка подписи вебхука Stripe
Stripe — единственный рецепт здесь, который не сводится к вставке, и обычно именно его уже пробовали до того, как пришли сюда. Заголовок Stripe-Signature — это список элементов, разделённых запятыми, а не подпись, и строка, которую подписала Stripe, — не одно тело запроса. Сама документация Stripe разбивает заголовок на строки ради читаемости; настоящий приходит одной строкой.
Stripe-Signature: t=1492774577, v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd, v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39
- Сообщение: значение t=, затем точка, затем сырое тело запроса ровно в том виде, в каком оно пришло. Для заголовка выше эта строка начинается с 1492774577. и тело идёт сразу за ней. Stripe называет её подписываемой нагрузкой, и именно в ней вся причина того, что подпись Stripe никогда не совпадает с одним телом.
- Ключ: секрет подписи именно этой конечной точки, целиком, вместе с префиксом whsec_. Это не ваш ключ API и не секрет другой конечной точки — один и тот же URL, зарегистрированный дважды, имеет два секрета.
- Кодировка ключа: Текст. Секрет подписи — строка, которую выбрала Stripe, и, хотя он выглядит случайным, он не шестнадцатеричный и не Base64; прочтение его как одного из двух даёт другие байты и подпись, которая ни с чем не совпадает.
- Подпись для проверки: только элемент v1, а не весь заголовок. v1= вставляется как есть, потому что эта метка отбрасывается точно так же, как sha256=, — а полный заголовок нет, потому что ведущее t= принимается за метку, и то, что идёт за ним, вовсе не подпись.
Об этом заголовке стоит знать ещё три вещи, прежде чем копать в другом месте. Игнорируйте любую схему, кроме v1: элемент v0 в тестовых событиях намеренно не является настоящей подписью, и игнорирование остального — как раз то, что не даёт навязать вам более слабую схему. Пока секрет конечной точки меняется, оба остаются действительными до суток, и заголовок несёт по одному элементу v1 на каждый секрет, из которых совпадёт лишь один. И каждая попытка доставки подписывается заново, поэтому повторная попытка не несёт подписи первой; метка времени внутри подписываемой строки — это ещё и то, что делает отказ от старой подписи настоящей защитой, а не жестом, потому что изменить её, не сломав подпись, нельзя.
Проверка подписи вебхука GitHub
GitHub — та самая вставка, вокруг которой этот инструмент и построен, и единственный рецепт здесь, который можно проверить от начала до конца, потому что GitHub публикует разобранный пример. Подпись приходит в X-Hub-Signature-256 в виде метки sha256= и следующего за ней шестнадцатеричного хеша, и эта метка здесь распознаётся и отбрасывается, поэтому значение заголовка вставляется ровно в том виде, в каком пришло.
X-Hub-Signature-256: sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
- Сообщение: сырое тело запроса, байт за байтом. Опубликованный GitHub пример подписывает тело Hello, World! и ничего после него — без перевода строки в конце.
- Ключ: секрет, который вы вписали в настройках вебхука, ровно так, как вписали. В опубликованном примере это It's a Secret to Everybody.
- Кодировка ключа: Текст. Секрет GitHub — строка, которую выбрали вы, поэтому он никогда не шестнадцатеричный: даже секрет, состоящий из одних шестнадцатеричных цифр, читается как набранные вами знаки.
- Подпись для проверки: всё значение заголовка вместе с sha256=. Шестнадцатеричное сравнивается без учёта регистра, так что неважно, в каком виде его печатают ваши логи.
Эти три значения вместе — самая быстрая проверка на здравость, какая есть: вставьте их, и страница сообщит о совпадении с HMAC-SHA-256 в шестнадцатеричном виде, а значит, и калькулятор, и ваше прочтение рецепта верны — ещё до того, как пробовать любое из них на настоящей доставке. GitHub присылает и более старый заголовок, X-Hub-Signature: это HMAC-SHA-1 по тому же телу, и он сохранён исключительно ради обратной совместимости. Поскольку эта страница считает все четыре хеша сразу, подпись из любого из двух заголовков распознаётся без того, чтобы вы говорили, из какого. Единственное, чего она спасти не может, — тело, пришедшее чем-то иным, чем отправленные GitHub байты: полезная нагрузка может нести знаки вне ASCII, и обе стороны обязаны читать их как UTF-8.
Проверка подписи вебхука Shopify
Shopify записывает свой хеш в Base64, а не в шестнадцатеричном виде, и здесь это единственная разница, которая важна: те же 32 байта превращаются в 44 знака, оканчивающиеся одним знаком равенства, и этот знак — заполнение, а не метка, поэтому спереди у него ничего не отрезается.
X-Shopify-Hmac-SHA256: dXEH6g6yUJ/CESIczphLijdXC211hsIsRvQ3nIsEPhc=
- Сообщение: сырое тело запроса, байт за байтом в том виде, в каком оно доставлено. Собственное предупреждение Shopify — о промежуточном слое, который разбирает тело: проверяйте сначала, разбирайте потом, потому что разборщик, уже превративший тело в объект, байты выбросил.
- Ключ: клиентский секрет того приложения, которому принадлежит вебхук. Не его токен доступа и не ключ API рядом в той же панели.
- Кодировка ключа: Текст. Как и в двух других случаях, секрет — это строка, а не запись байтов.
- Подпись для проверки: всё значение заголовка. Оно вставляется как есть, и, поскольку против каждого хеша пробуются и шестнадцатеричное, и Base64, сообщённая страницей кодировка сама и есть ответ на вопрос, в каком виде записал его отправитель.
Значение в блоке выше стоит там, чтобы показать форму: это те же 32 байта, что в примере GitHub, записанные в Base64 вместо шестнадцатеричного вида. Их стоит увидеть рядом, потому что в этом и состоит всё содержание разницы между двумя заголовками — один хеш, две записи, — и именно поэтому страница называет кодировку рядом с алгоритмом, а не просто сообщает, что что-то совпало.
Частые вопросы
- Моя подпись не совпадает. Что проверить первым?
- Кодировку ключа, затем байты сообщения. Секрет, похожий на шестнадцатеричный, часто и задуман как шестнадцатеричный, а не как текст, и оба варианта дают совершенно разные подписи без всякой ошибки. Затем убедитесь, что подписываете сырое тело ровно в том виде, в каком оно пришло, а не пересобранную копию.
- Почему 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; сообщение и ключ никогда не покидают ваше устройство.
- Как проверить подпись вебхука Stripe?
- Соедините три вещи в одну строку — метку времени из элемента t= заголовка, точку и сырое тело запроса — и подпишите её секретом подписи конечной точки, прочитанным как текст; затем сравните результат с элементом v1 заголовка. Раздел выше проходит это поле за полем. Спотыкаются почти все на первом шаге: одно тело — не то, что подписала Stripe.
- Как проверить подпись вебхука GitHub?
- Подпишите сырое тело запроса секретом вебхука, прочитанным как текст, алгоритмом SHA-256, и сравните шестнадцатеричный хеш с заголовком X-Hub-Signature-256. Метку sha256= при вставке сюда можно оставить. GitHub публикует разобранный пример — тело Hello, World! с секретом It's a Secret to Everybody — и это самый быстрый способ проверить своё прочтение рецепта, прежде чем пробовать его на настоящей доставке.
- Как проверить подпись вебхука Shopify?
- Подпишите сырое тело запроса клиентским секретом того приложения, которому принадлежит вебхук, прочитанным как текст, алгоритмом SHA-256, и сравните хеш в Base64 с заголовком X-Shopify-Hmac-SHA256. Всё значение заголовка вставляется как есть — знак равенства в конце это заполнение Base64. Когда не совпадает, обычная причина — промежуточный слой, разобравший тело раньше, чем его увидел ваш обработчик.
- Как проверить подпись вебхука любого другого поставщика?
- Дело решают четыре вопроса, и в документации отправителя есть все четыре: какой заголовок несёт подпись, какая строка подписывается на самом деле, как следует читать секрет и шестнадцатеричный хеш или Base64. Перенесите это в поля выше. Если и тогда не совпадает, ответ почти всегда второй: очень многие отправители подписывают тело вместе с чем-то присоединённым к нему, а не одно тело.
Похожие инструменты
- Отладчик кодов TOTP / 2FA
Генерируйте и отлаживайте коды 2FA с полным выводом.
- Bcrypt
Создайте хеш bcrypt или проверьте пароль по готовому.
- Декодер / проверка JWT
Декодирует и проверяет JSON Web Token — подпись и claims.
- Генератор хешей
MD5, SHA-1, SHA-256, SHA-384 и SHA-512 сразу.