HMAC (Hash-based Message Authentication Code) используется для проверки целостности и подлинности данных на основе криптографических хеш-функций и секретного ключа. В отличие от обычного хеша, который лишь фиксирует состояние данных, HMAC добавляет фактор секретности, что позволяет не только обнаруживать изменения, но и подтверждать источник сообщения.
В основе HMAC лежит комбинация:
Любое изменение входных данных или ключа приводит к совершенно другому значению HMAC, что делает подделку практически невозможной без знания ключа.
Библиотека Crypto-js предоставляет готовые инструменты для работы с HMAC без необходимости вручную реализовывать алгоритм.
Подключение базового набора:
import CryptoJS from "crypto-js";
или в Node.js:
const CryptoJS = require("crypto-js");
Основной метод:
CryptoJS.HmacSHA256(message, secretKey)
Где:
message — исходные данныеsecretKey — секретный ключHmacSHA256 — алгоритм хешированияПростейший пример формирования кода аутентификации сообщения:
const message = "transfer=100&to=account123";
const secretKey = "super_secret_key";
const hmac = CryptoJS.HmacSHA256(message, secretKey).toString();
console.log(hmac);
Результатом будет строка фиксированной длины, зависящая от входных данных и ключа.
Важно понимать, что одинаковые входные данные при одном и том же ключе всегда дают одинаковый результат, что делает HMAC удобным для проверки целостности.
Crypto-js поддерживает несколько вариантов:
CryptoJS.HmacMD5(message, key)
CryptoJS.HmacSHA1(message, key)
CryptoJS.HmacSHA256(message, key)
CryptoJS.HmacSHA512(message, key)
На практике чаще всего используется SHA-256 и выше, поскольку MD5 и SHA-1 считаются криптографически слабыми.
По умолчанию Crypto-js возвращает объект WordArray, который необходимо преобразовать в строку:
const hmacHex = CryptoJS.HmacSHA256(message, secretKey).toString(CryptoJS.enc.Hex);
Другие варианты кодирования:
.toString(CryptoJS.enc.Base64)
Hex удобен для логов и API, Base64 — для компактной передачи данных.
Основной сценарий использования HMAC — проверка того, что данные не были изменены.
const payload = JSON.stringify({
userId: 42,
amount: 500
});
const secret = "shared_secret";
const signature = CryptoJS.HmacSHA256(payload, secret).toString();
const receivedPayload = JSON.stringify({
userId: 42,
amount: 500
});
const receivedSignature = "полученная_подпись";
const expectedSignature = CryptoJS.HmacSHA256(receivedPayload, secret).toString();
const isValid = expectedSignature === receivedSignature;
Если данные изменены хотя бы на один символ, подписи не совпадут.
Критический аспект использования HMAC — стабильность входных данных. JSON-строки могут отличаться по порядку ключей, что приведёт к разным HMAC.
Пример проблемы:
{ "a": 1, "b": 2 }
{ "b": 2, "a": 1 }
Хотя объекты логически одинаковы, строки различны.
Решение — нормализация:
function stableStringify(obj) {
return JSON.stringify(obj, Object.keys(obj).sort());
}
Использование:
const payload = stableStringify(data);
const hmac = CryptoJS.HmacSHA256(payload, secret).toString();
Типичный сценарий — подпись HTTP-запросов.
const timestamp = Date.now();
const body = JSON.stringify({ action: "pay", sum: 100 });
const dataToSign = `${timestamp}.${body}`;
const signature = CryptoJS.HmacSHA256(dataToSign, secretKey).toString();
Передача:
fetch("/api/payment", {
method: "POST",
headers: {
"X-Signature": signature,
"X-Timestamp": timestamp
},
body
});
На сервере выполняется идентичное вычисление и сравнение.
HMAC сам по себе не защищает от повторного использования валидной подписи. Для этого добавляется временная метка:
const allowedDrift = 5 * 60 * 1000; // 5 минут
Проверка:
if (Math.abs(Date.now() - timestamp) > allowedDrift) {
// отклонить запрос
}
Комбинация HMAC + timestamp значительно повышает устойчивость системы.
Безопасность HMAC полностью зависит от секретного ключа. При его компрометации защита теряется.
Практики хранения:
Пример:
const secretKey = process.env.HMAC_SECRET;
Недопустимо:
1. Использование нестабильных данных
2. Разные алгоритмы на клиенте и сервере
3. Неправильное кодирование результата
4. Использование слабых ключей
Crypto-js реализует алгоритмы на JavaScript, что делает его удобным, но не самым быстрым решением для больших объёмов данных.
Особенности:
Аналог HMAC в Node.js:
const crypto = require("crypto");
const hmac = crypto
.createHmac("sha256", secretKey)
.update(message)
.digest("hex");
Crypto-js:
CryptoJS.HmacSHA256(message, secretKey).toString();
Crypto-js удобнее для универсального кода (browser + Node.js), но Node.js crypto предпочтительнее для серверных высоконагруженных систем.
HMAC активно используется в:
Типовой паттерн — shared secret между двумя доверенными сторонами, где HMAC выступает гарантом неизменности сообщения.
При работе с большими проектами удобно выделять функцию подписи:
function signPayload(payload, secret) {
const normalized = typeof payload === "string"
? payload
: JSON.stringify(payload);
return CryptoJS.HmacSHA256(normalized, secret).toString();
}
И функцию проверки:
function verifyPayload(payload, signature, secret) {
const expected = signPayload(payload, secret);
return expected === signature;
}
Такой подход уменьшает вероятность ошибок и упрощает сопровождение кода.