Метод subtle.verify

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

Promise, возвращаемый методом, резолвится в true или false в зависимости от результата проверки.


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

Возвращаемое значение:

Promise<boolean>

Параметры

algorithm

Объект, описывающий алгоритм проверки подписи. Структура зависит от выбранного механизма:

  • RSASSA-PKCS1-v1_5
  • RSA-PSS
  • ECDSA
  • HMAC

Примеры:

{
  name: "RSASSA-PKCS1-v1_5"
}
{
  name: "ECDSA",
  hash: "SHA-256"
}

Для RSA-PSS дополнительно могут задаваться параметры:

{
  name: "RSA-PSS",
  saltLength: 32
}

key

Ключ проверки подписи (CryptoKey), полученный через:

  • crypto.subtle.importKey
  • или сгенерированный через crypto.subtle.generateKey

Особенность: это всегда публичный ключ для асимметричных алгоритмов или симметричный ключ для HMAC.


signature

Подпись, которую необходимо проверить.

Тип данных:

  • ArrayBuffer
  • TypedArray (передаётся как буфер)
  • DataView

Подпись должна быть в том же формате, в котором она была создана методом sign.


data

Исходные данные, которые были подписаны.

Требование:

  • строго идентичный набор байтов, который использовался при создании подписи
  • любые изменения (включая пробелы, кодировку, порядок байтов) приведут к false

Основной принцип работы

Метод verify математически проверяет соответствие:

  • подписи
  • публичного ключа
  • исходных данных

Результат не выбрасывает ошибку при неверной подписи — вместо этого возвращается false.


Пример использования HMAC

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

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

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

Подпись

const data = new TextEncoder().encode("hello world");

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

Проверка

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

console.log(result);

Пример RSA (RSASSA-PKCS1-v1_5)

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

const keys = await crypto.subtle.generateKey(
  {
    name: "RSASSA-PKCS1-v1_5",
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"
  },
  true,
  ["sign", "verify"]
);

Подпись

const data = new TextEncoder().encode("message");

const signature = await crypto.subtle.sign(
  "RSASSA-PKCS1-v1_5",
  keys.privateKey,
  data
);

Проверка

const valid = await crypto.subtle.verify(
  "RSASSA-PKCS1-v1_5",
  keys.publicKey,
  signature,
  data
);

Пример ECDSA

ECDSA требует указания хэш-функции.

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

const keys = await crypto.subtle.generateKey(
  {
    name: "ECDSA",
    namedCurve: "P-256"
  },
  true,
  ["sign", "verify"]
);

Подпись

const data = new TextEncoder().encode("data");

const signature = await crypto.subtle.sign(
  {
    name: "ECDSA",
    hash: "SHA-256"
  },
  keys.privateKey,
  data
);

Проверка

const ok = await crypto.subtle.verify(
  {
    name: "ECDSA",
    hash: "SHA-256"
  },
  keys.publicKey,
  signature,
  data
);

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

1. Кодировка данных

verify работает только с бинарными данными.

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

const encoder = new TextEncoder();
const data = encoder.encode("text");

2. Immutable nature данных

Любое изменение входных байтов делает подпись недействительной:

  • изменение регистра символов
  • добавление пробелов
  • различия в UTF-8 представлении

3. Формат подписи

Подпись должна быть передана в исходном бинарном виде.

Если подпись сериализуется (например, в Base64), её необходимо декодировать:

function base64ToBuffer(base64) {
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);

  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }

  return bytes.buffer;
}

Частые причины ошибки false

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

Самая частая причина — различие между исходными данными и проверяемыми.


Несовместимый алгоритм

Подпись и проверка должны использовать одинаковые параметры:

  • алгоритм
  • hash
  • saltLength (RSA-PSS)
  • curve (ECDSA)

Неправильный ключ

  • попытка использовать privateKey вместо publicKey
  • использование ключа из другого набора

Повреждённая подпись

  • ошибка кодирования Base64
  • потеря байтов при передаче через JSON

Типичные сценарии использования

Проверка JWT (частично)

Хотя JWT обычно обрабатывается специализированными библиотеками, механизм проверки подписи основан на verify.


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

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

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

Валидация файлов

Подпись файла проверяется перед использованием:

const valid = await crypto.subtle.verify(
  "RSASSA-PKCS1-v1_5",
  publicKey,
  signature,
  fileBuffer
);

Особенности производительности

  • операции выполняются асинхронно
  • зависят от аппаратной поддержки WebCrypto
  • RSA-2048 значительно медленнее HMAC
  • ECDSA обычно быстрее RSA при равной стойкости

Безопасные практики

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

Различие поведения при ошибках

Метод не выбрасывает исключение при неверной подписи.

Ошибки возникают только при:

  • неверных типах параметров
  • неподдерживаемом алгоритме
  • некорректном ключе

Во всех остальных случаях возвращается false, что важно учитывать при построении логики проверки.