Как вычислить HMAC в Base64
- Выбраны режим HMAC и SHA-256. Если ваш API использует SHA-1, SHA-384, SHA-512 или MD5, выберите другой вариант в списке Алгоритм.
- Введите Секретный ключ и выставьте Формат ключа: «Текст (UTF-8)» для обычной строки-секрета, Hex или Base64, если ключ дан закодированными байтами.
- Введите или вставьте сообщение в поле Текст либо переключите Источник на Файл и перетащите точный файл с телом запроса.
- Скопируйте подпись в Base64, Base64URL или hex. Блок Все алгоритмы показывает HMAC сразу со всеми хешами. Это выручает, когда документация невнятно говорит, какой из них используется.
Пустой ключ принимается, и результат всё равно будет, но с пометкой, что такой HMAC ничего не защищает.
Примеры значений
С ключом key и сообщением The quick brown fox jumps over the lazy dog:
| Алгоритм | HMAC в Base64 |
|---|---|
| HMAC-MD5 | gAcHE0Y+d0m5DC3CSRHidQ== |
| HMAC-SHA1 | 3nybhbi3iqa8ino29wqQcBydtNk= |
| HMAC-SHA256 | 97yD9DBThCSxMpjmqm+xQ+9NWaFJRhdZl0edvC0aPNg= |
| HMAC-SHA512 | tCrwkFe6weLUFwjkipAuCbX/fxKrQopP6GZTxz3SSPuC+UilSfe3kaW0GRXuTR7Dk1NX5OIxclDQNyr6Lr7rOg== |
В hex значение HMAC-SHA256 равно f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8; это известный тестовый вектор из статьи Википедии про HMAC. Кодировка ключа меняет всё: текст a2V5 в роли ключа даёт 5O+7AtpguFrgIRw1qXGU+m0OaTs1oV1kwRQ/hI1WAUE=, а a2V5, прочитанный как Base64, даёт байты key, и результат снова 97yD9DBT….
Подписи вебхуков: Base64 или hex
Провайдеры сходятся на HMAC, но не на кодировке результата и не на том, что именно подписывается. Несколько хорошо документированных примеров:
| Провайдер | Заголовок | Алгоритм | Кодировка | Что подписывается |
|---|---|---|---|---|
| Shopify | X-Shopify-Hmac-Sha256 | HMAC-SHA256 | Base64 | исходное тело запроса |
| Standard Webhooks (Svix) | webhook-signature: v1,… | HMAC-SHA256 | Base64 | id.timestamp.body, ключ из Base64 после whsec_ |
| Twilio | X-Twilio-Signature | HMAC-SHA1 | Base64 | URL и отсортированные POST-параметры |
| GitHub | X-Hub-Signature-256: sha256=… | HMAC-SHA256 | hex | исходное тело запроса |
| Stripe | Stripe-Signature: t=…,v1=… | HMAC-SHA256 | hex | timestamp.body |
Если значение из этого инструмента совпадает в hex, а ваш код сравнивает Base64 (или наоборот), вычисление верное, различается только кодировка. Что именно подписывается, смотрите в документации провайдера; префиксы с меткой времени нужны, чтобы нельзя было повторно отправить старые запросы.
Проверка подписи в коде
Сравнивайте «сырые» байты за постоянное время и следите, чтобы хешировалось тело ровно в том виде, в каком пришло:
const crypto = require("crypto");
function verifyWebhook(rawBody, secret, signatureHeader) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest();
const given = Buffer.from(signatureHeader, "base64");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}
Если передать Buffer с фразой про лису как rawBody, ключ key как секрет и значение Base64 из таблицы как подпись, функция вернёт true. Подсуньте ей JSON.stringify(JSON.parse(body)) вместо исходного тела, и она вернёт false, как только в JSON провайдера окажется пробел, который ваш сериализатор выбрасывает. В Express это значит, что на маршруте вебхука тело нужно читать через express.raw().
В Python то же самое делается через hmac.new(key, body, hashlib.sha256).digest(), результат кодируется в Base64 и проверяется hmac.compare_digest. В командной строке openssl dgst -sha256 -hmac 'key' -binary | base64 даёт тот же Base64; если ключ в hex, используйте -mac HMAC -macopt hexkey:…. Для надёжного ключа пресет «Секрет HMAC» в генераторе ключей Base64 создаёт 32 случайных байта. Подписанные токены (в отличие от подписанных запросов) собирает кодировщик JWT.
Вопросы и ответы
Почему HMAC вебхука не совпадает?
Подпись считается по точным байтам тела запроса. Если фреймворк разобрал JSON, а вы хешируете заново сериализованную копию, меняются пробелы или порядок ключей, и результат расходится. Хешируйте исходное тело, проверьте, ждёт ли провайдер hex или Base64, и нужно ли сначала декодировать секрет.
Секрет HMAC задаётся текстом или в Base64?
Зависит от провайдера. Чаще всего выдаётся строка, которая используется как текст в UTF-8. Некоторые выдают ключ в Base64, иногда после префикса вроде whsec_, и перед использованием его нужно декодировать в байты. Выставьте Формат ключа соответственно.
Чем хеш отличается от HMAC?
SHA-256 от сообщения может посчитать кто угодно, поэтому хеш обнаруживает только случайные изменения. HMAC подмешивает секретный ключ, и корректное значение может получить только владелец ключа; это и делает HMAC подписью.
Можно ли использовать HMAC-MD5 или HMAC-SHA1?
Оба до сих пор используются в старых API и остаются гораздо надёжнее простых MD5 или SHA-1, потому что коллизии не дают подделать HMAC. Для новых систем выбирайте HMAC-SHA256.