HMAC в протоколах аутентификации API

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

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


Принцип работы HMAC

HMAC формируется по следующей логике:

  1. Берётся исходное сообщение (например, HTTP-запрос)
  2. Добавляется секретный ключ
  3. Выполняется двойное хеширование с использованием криптографической функции (SHA-256, SHA-1 и др.)

Формально:

(K, m) = H((K opad) ;||; H((K ipad) ;||; m))

Где:

    1. — секретный ключ
    1. — сообщение
    1. — хеш-функция
  • (opad), (ipad) — фиксированные паддинги

Роль HMAC в API-аутентификации

В API HMAC используется для:

  • подтверждения подлинности клиента
  • защиты от подмены параметров запроса
  • предотвращения повторного воспроизведения (replay attacks)
  • обеспечения целостности данных при передаче

Типичный сценарий:

  1. Клиент формирует запрос
  2. Генерирует подпись HMAC на основе параметров запроса
  3. Отправляет запрос + подпись
  4. Сервер пересчитывает HMAC и сравнивает значения

CryptoJS как инструмент реализации HMAC

Библиотека CryptoJS предоставляет удобные функции для генерации HMAC с различными алгоритмами.

Поддерживаемые варианты:

  • HmacMD5
  • HmacSHA1
  • HmacSHA256
  • HmacSHA512

На практике в API чаще используется HMAC-SHA256.


Установка и подключение CryptoJS

import CryptoJS from "crypto-js";

или через CDN:

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>

Базовая генерация HMAC

const secretKey = "my_super_secret_key";
const message = "user_id=42&action=transfer";

const signature = CryptoJS.HmacSHA256(message, secretKey).toString();

console.log(signature);

Результат — строка в шестнадцатеричном формате, которая используется как подпись запроса.


Формирование подписи для API-запроса

В реальных API подпись формируется не из произвольной строки, а из строго определённого канонического запроса.

Пример структуры:

  • HTTP метод
  • endpoint
  • query параметры
  • тело запроса
  • timestamp

Пример канонизации данных

const method = "POST";
const endpoint = "/api/v1/transfer";
const timestamp = Date.now();
const body = JSON.stringify({
  from: "A1",
  to: "B2",
  amount: 500
});

const payload = `${method}\n${endpoint}\n${timestamp}\n${body}`;

Генерация HMAC подписи для запроса

const secretKey = "super_secret_key";

const signature = CryptoJS
  .HmacSHA256(payload, secretKey)
  .toString(CryptoJS.enc.Hex);

Передача подписи в HTTP-запросе

Обычно подпись передаётся в заголовках:

fetch("https://api.example.com/api/v1/transfer", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-TIMESTAMP": timestamp,
    "X-API-SIGNATURE": signature
  },
  body: body
});

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

Сервер выполняет идентичные действия:

  1. Берёт параметры запроса
  2. Формирует каноническую строку
  3. Генерирует HMAC с тем же ключом
  4. Сравнивает подписи

Псевдологика проверки:

if (serverSignature === clientSignature) {
  // запрос валиден
} else {
  // отклонить запрос
}

Использование временных меток (timestamp)

Timestamp защищает от повторной отправки запроса.

Пример:

const timestamp = Date.now();

Сервер проверяет допустимое окно времени (например, ±5 минут).


Защита от replay-атак

Без timestamp и уникальных параметров запрос можно перехватить и повторить.

Механизмы защиты:

  • timestamp
  • nonce (одноразовый идентификатор)
  • включение всех параметров в подпись

Использование nonce

const nonce = CryptoJS.lib.WordArray.random(16).toString();

Добавляется в подпись:

const payload = `${method}\n${endpoint}\n${timestamp}\n${nonce}\n${body}`;

Типичные ошибки при работе с HMAC

1. Несогласованность канонизации

Разный порядок параметров приводит к разным подписям.

2. Разные форматы данных

JSON с пробелами и без — это разные строки.

3. Неправильное кодирование

Base64 vs Hex может привести к несовпадению подписи.

CryptoJS.HmacSHA256(payload, key).toString(CryptoJS.enc.Base64);

HMAC-SHA256 vs другие алгоритмы

  • MD5 — устаревший, небезопасный
  • SHA1 — устаревший
  • SHA256 — стандарт де-факто
  • SHA512 — повышенная безопасность, но больше вычислений

Практический шаблон API-клиента с HMAC

import CryptoJS from "crypto-js";

const API_SECRET = "secret";
const API_KEY = "public_key";

function signRequest(method, url, body = "") {
  const timestamp = Date.now();

  const payload = `${method}\n${url}\n${timestamp}\n${body}`;

  const signature = CryptoJS
    .HmacSHA256(payload, API_SECRET)
    .toString(CryptoJS.enc.Hex);

  return {
    timestamp,
    signature
  };
}

async function request() {
  const method = "POST";
  const url = "/api/v1/transfer";
  const body = JSON.stringify({ amount: 100 });

  const { timestamp, signature } = signRequest(method, url, body);

  return fetch(url, {
    method,
    headers: {
      "X-API-KEY": API_KEY,
      "X-API-TIMESTAMP": timestamp,
      "X-API-SIGNATURE": signature,
      "Content-Type": "application/json"
    },
    body
  });
}

Криптографическая устойчивость HMAC

Безопасность HMAC основана на свойствах:

  • стойкость хеш-функции к коллизиям
  • невозможность восстановления ключа из подписи
  • устойчивость к length extension attack (в отличие от обычных хешей)

Применение в реальных API

HMAC используется в:

  • платежных системах
  • криптовалютных биржах
  • облачных API (AWS-style signing)
  • банковских интеграциях
  • приватных REST и GraphQL API

Сравнение с JWT

HMAC:

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

JWT:

  • содержит payload + подпись
  • используется для авторизации сессий
  • менее гибок для подписи каждого запроса

Производительность CryptoJS HMAC

CryptoJS реализован на JavaScript и подходит для:

  • клиентских приложений
  • небольших нагрузок
  • браузерных API-клиентов

Для серверных high-load систем чаще используют нативные крипто-библиотеки Node.js.