Метод subtle.verify для HMAC

Метод crypto.subtle.verify используется для проверки цифровой подписи или кода аутентичности сообщения. В случае HMAC он применяется для верификации целостности и подлинности данных на основе симметричного ключа.

HMAC (Hash-based Message Authentication Code) представляет собой механизм, который сочетает криптографическую хеш-функцию и секретный ключ. Метод verify позволяет сравнить переданный MAC (message authentication code) с вычисленным значением, не раскрывая при этом сам секретный ключ.


Общая сигнатура метода

Метод вызывается через интерфейс SubtleCrypto:

crypto.subtle.verify(
    algorithm,
    key,
    signature,
    data
);

Параметры:

algorithm Объект, описывающий используемый алгоритм. Для HMAC это:

{ name: "HMAC" }

key Ключ типа CryptoKey, полученный через crypto.subtle.importKey или crypto.subtle.generateKey. Ключ обязательно должен быть:

  • algorithm: HMAC
  • usages: ["verify"] или ["sign", "verify"]
  • extractable: false (в большинстве безопасных сценариев)

signature Буфер ArrayBuffer или TypedArray, содержащий HMAC-код, который требуется проверить.


data Данные (ArrayBuffer или TypedArray), для которых выполняется проверка.


Возвращаемое значение Promise, который разрешается в логическое значение:

  • true — подпись совпадает
  • false — подпись не совпадает

Принцип работы HMAC-проверки

Проверка HMAC не расшифровывает данные и не восстанавливает ключ. Вместо этого происходит повторное вычисление MAC:

  1. Берётся исходное сообщение
  2. Используется тот же секретный ключ
  3. Вычисляется HMAC
  4. Результат сравнивается с переданным значением

Если значения совпадают — данные считаются подлинными и неизменёнными.


Создание HMAC-ключа

Перед использованием verify необходимо создать или импортировать ключ.

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

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

Пример подписи и проверки

Подготовка данных

const encoder = new TextEncoder();
const data = encoder.encode("сообщение для проверки");

Создание подписи

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

Проверка подписи через verify

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

Результат

console.log(isValid); // true

Поведение при изменении данных

Любое изменение входного сообщения приводит к изменению результата проверки.

const tampered = encoder.encode("изменённое сообщение");

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

console.log(isValid); // false

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

1. Сравнение выполняется безопасно

Внутри реализации используется защищённое сравнение, предотвращающее timing-атаки. Это исключает возможность определения совпадения по времени выполнения.


2. Ключ должен совпадать с используемым при sign

Проверка возможна только при использовании того же CryptoKey, что применялся для генерации подписи. Даже незначительное расхождение делает результат false.


3. Формат signature

Поддерживаются:

  • ArrayBuffer
  • TypedArray (Uint8Array и др.)

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


Использование с импортированным ключом

Импорт ключа

const rawKey = encoder.encode("secret-key");

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

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

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

Практическое применение

Проверка целостности сообщений

HMAC используется для подтверждения, что данные:

  • не были изменены
  • были созданы доверенной стороной
  • не были подделаны в процессе передачи

API-аутентификация

В веб-сервисах HMAC часто применяется для:

  • подписи запросов
  • проверки токенов
  • защиты webhook-запросов

Пример проверки webhook

const expectedSignature = await crypto.subtle.verify(
    "HMAC",
    secretKey,
    receivedSignature,
    requestBody
);

Если результат false, запрос отклоняется.


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

Несовпадение кодировки данных

Разные способы преобразования строки в байты приводят к различным HMAC:

TextEncoder().encode("text")

и

Buffer.from("text")

могут давать разные результаты в зависимости от окружения.


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

Ключ должен быть именно HMAC-типа. RSA или ECDSA ключи несовместимы с verify для HMAC.


Повторное использование повреждённой подписи

Даже один изменённый байт делает подпись недействительной.


Сравнение с ручной проверкой

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

if (computed === signature) // небезопасно

Причины:

  • риск timing-атак
  • различие бинарных форматов
  • некорректная обработка буферов

Метод crypto.subtle.verify решает эти проблемы на уровне реализации.


Асинхронная природа метода

verify всегда возвращает Promise, так как криптографические операции выполняются вне основного потока выполнения JavaScript.

await crypto.subtle.verify(...)

Это важно учитывать при построении цепочек обработки данных и серверной логики в браузере.


Работа с бинарными данными

Все входные параметры метода работают на уровне байтов. Это означает:

  • строки должны быть сериализованы
  • JSON необходимо кодировать через TextEncoder
  • файлы читаются через ArrayBuffer

Обобщённый цикл HMAC-проверки

  1. Получение данных
  2. Получение подписи
  3. Использование того же ключа
  4. Вызов crypto.subtle.verify
  5. Получение boolean-результата

Связь с crypto.subtle.sign

Метод verify всегда используется в паре с:

  • crypto.subtle.sign — создание HMAC
  • crypto.subtle.verify — проверка HMAC

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