HMAC: принцип работы и назначение

HMAC (Hash-based Message Authentication Code) представляет собой механизм вычисления кода аутентификации сообщения на основе криптографической хеш-функции и секретного ключа. Его назначение — одновременно обеспечить целостность данных и их подлинность.

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

Математически HMAC строится на следующей идее:

  • входные данные проходят два этапа хеширования
  • используется секретный ключ фиксированной длины (или приведённый к ней)
  • применяются внутренние и внешние паддинги (ipad, opad)

Это делает HMAC устойчивым к атакам, связанным с коллизиями хеш-функций, при условии, что сама хеш-функция криптографически стойкая (например, SHA-256).


Назначение HMAC в прикладных системах

HMAC используется в ситуациях, где необходимо доказать:

  • что сообщение не было изменено
  • что сообщение создано обладателем секретного ключа

Типовые сценарии:

  • API аутентификация запросов
  • подпись webhook-событий
  • защита токенов сессионной передачи
  • контроль целостности сообщений в распределённых системах

В отличие от цифровой подписи, HMAC является симметричным механизмом: один и тот же ключ используется для генерации и проверки кода.


Отличие HMAC от обычных хеш-функций

Обычная хеш-функция:

  • не использует секрет
  • не обеспечивает аутентификацию
  • может быть пересчитана любым участником

HMAC:

  • требует секретного ключа
  • позволяет проверить происхождение данных
  • защищён от подмены без знания ключа

Ключевой момент: даже зная алгоритм и входные данные, невозможно восстановить корректный HMAC без ключа.


Web Crypto API и HMAC

В браузерной среде HMAC реализуется через интерфейс SubtleCrypto, доступный как crypto.subtle.

Основные методы:

  • generateKey
  • importKey
  • sign
  • verify

Алгоритм задаётся через объект конфигурации:

{
  name: "HMAC",
  hash: { name: "SHA-256" }
}

Поддерживаемые хеш-функции зависят от окружения, чаще всего используются:

  • SHA-256
  • SHA-384
  • SHA-512

Генерация ключа HMAC

Ключ создаётся через generateKey:

const key = await crypto.subtle.generateKey(
  {
    name: "HMAC",
    hash: { name: "SHA-256" }
  },
  true,
  ["sign", "verify"]
);

Параметры:

  • true — ключ можно экспортировать
  • sign — разрешение на подпись
  • verify — разрешение на проверку

Ключ в WebCrypto представлен как CryptoKey, а не как строка или байтовый массив.


Импорт ключа

В реальных системах ключ чаще приходит извне и импортируется:

const rawKey = new Uint8Array([1, 2, 3, 4, 5, 6]);

const key = await crypto.subtle.importKey(
  "raw",
  rawKey,
  {
    name: "HMAC",
    hash: { name: "SHA-256" }
  },
  false,
  ["sign", "verify"]
);

Формат "raw" означает использование «сырых» байтов ключа.


Формирование HMAC-подписи

Метод sign возвращает бинарный результат (ArrayBuffer):

const encoder = new TextEncoder();
const data = encoder.encode("сообщение");

const signature = await crypto.subtle.sign(
  "HMAC",
  key,
  data
);

Результат необходимо преобразовывать для удобного хранения или передачи, например в Base64:

function toBase64(buffer) {
  const bytes = new Uint8Array(buffer);
  let binary = "";
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

const sigBase64 = toBase64(signature);

Проверка HMAC-подписи

Для проверки используется verify:

const valid = await crypto.subtle.verify(
  "HMAC",
  key,
  signature,
  data
);

Результат:

  • true — подпись корректна
  • false — данные или ключ не совпадают

Особенности работы с данными

WebCrypto API работает исключительно с бинарными структурами:

  • ArrayBuffer
  • TypedArray

Строки необходимо явно кодировать:

const encoder = new TextEncoder();
const encoded = encoder.encode("текст");

И декодировать при необходимости:

const decoder = new TextDecoder();
const decoded = decoder.decode(encoded);

Важные аспекты безопасности

Секретность ключа

Ключ HMAC должен оставаться строго конфиденциальным. Его компрометация полностью нарушает модель безопасности.

Отсутствие необходимости в nonce

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

Устойчивость к подмене

HMAC защищён от:

  • простого перебора хеша
  • атак на коллизии хеш-функций (при корректном выборе SHA-256 и выше)

Типовые ошибки при использовании WebCrypto HMAC

Неправильная кодировка данных

Одна из самых частых проблем — сравнение строк вместо байтов. Даже различие в UTF-8 кодировке приводит к разным результатам.

Потеря бинарного формата

HMAC нельзя корректно сравнивать как строку без преобразования:

  • ArrayBuffer ≠ строка
  • Base64 или hex обязательны для хранения

Использование неподходящего ключа

Ключ должен соответствовать требованиям алгоритма. Короткие или предсказуемые ключи снижают стойкость.


Совместимость и переносимость

HMAC в Web Crypto API стандартизирован и поддерживается в современных браузерах:

  • Chrome
  • Firefox
  • Edge
  • Safari (с ограничениями в старых версиях)

В серверной среде аналогичный API отсутствует, но концептуально совместим с Node.js crypto модулем при использовании HMAC-SHA256.


Пример полного цикла работы

const key = await crypto.subtle.generateKey(
  {
    name: "HMAC",
    hash: { name: "SHA-256" }
  },
  true,
  ["sign", "verify"]
);

const encoder = new TextEncoder();
const message = encoder.encode("данные");

const signature = await crypto.subtle.sign(
  "HMAC",
  key,
  message
);

const isValid = await crypto.subtle.verify(
  "HMAC",
  key,
  signature,
  message
);

Такой цикл отражает базовую модель применения HMAC в браузере: генерация ключа, подпись данных и проверка целостности.


Практическое значение в архитектуре приложений

HMAC часто выступает как слой защиты поверх обычного HTTP-трафика. Он применяется:

  • для проверки подлинности клиента без передачи пароля
  • для защиты REST-запросов
  • для синхронизации состояния между сервисами

Его простота и высокая скорость вычисления делают его предпочтительным в системах, где требуется частая проверка целостности сообщений без затрат на асимметричную криптографию.