Метод subtle.sign

crypto.subtle.sign — это метод Web Crypto API, предназначенный для создания цифровой подписи над произвольными данными с использованием криптографического ключа. Результат операции представляет собой бинарную подпись (ArrayBuffer), которая затем может быть использована для проверки целостности данных и аутентичности отправителя через crypto.subtle.verify.

Метод работает асинхронно и возвращает Promise<ArrayBuffer>, что позволяет выполнять криптографические операции без блокировки основного потока выполнения JavaScript.


crypto.subtle.sign(algorithm, key, data)

Параметры

algorithm

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

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

Каждый алгоритм требует специфической конфигурации.

Примеры:

{ name: "HMAC", hash: "SHA-256" }
{
  name: "RSA-PSS",
  saltLength: 32
}
{
  name: "ECDSA",
  hash: "SHA-256"
}

key

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

Ключ должен быть создан или импортирован с соответствующим назначением:

  • sign должен быть включён в keyUsages
  • для HMAC используется симметричный ключ
  • для RSA и ECDSA используется приватный ключ

Пример генерации:

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

data

Данные, которые необходимо подписать. Представляют собой BufferSource:

  • ArrayBuffer
  • TypedArray (Uint8Array, etc.)

Перед подписанием данные обычно кодируются в бинарный формат:

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

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

Метод возвращает:

Promise<ArrayBuffer>

Это бинарная подпись, формат которой зависит от алгоритма:

  • HMAC → фиксированный размер хэша
  • RSA-PSS / RSA-PKCS1 → бинарный блок
  • ECDSA → DER-кодированная подпись (ASN.1 структура)

Алгоритмы и особенности работы

HMAC

Используется симметричный ключ. Подходит для проверки целостности данных в доверенной среде.

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

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

Особенность: один и тот же ключ используется для подписи и проверки.


RSA-PSS

Современный RSA-алгоритм подписи с вероятностным паддингом. Более безопасен, чем PKCS#1 v1.5.

const signature = await crypto.subtle.sign(
  {
    name: "RSA-PSS",
    saltLength: 32
  },
  privateKey,
  data
);

Критически важно, чтобы saltLength совпадал при верификации.


RSASSA-PKCS1-v1_5

Более старый RSA-алгоритм. Широко поддерживается, но менее устойчив к некоторым типам атак.

const signature = await crypto.subtle.sign(
  {
    name: "RSASSA-PKCS1-v1_5"
  },
  privateKey,
  data
);

ECDSA

Алгоритм на эллиптических кривых. Используется в современных системах, включая криптовалютные и TLS-сертификаты.

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

Особенность: подпись возвращается в формате DER и требует дополнительного разбора при низкоуровневой работе.


Пример полного сценария: создание подписи

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

// генерация ключевой пары
const keyPair = await crypto.subtle.generateKey(
  {
    name: "RSA-PSS",
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"
  },
  true,
  ["sign", "verify"]
);

// создание подписи
const signature = await crypto.subtle.sign(
  {
    name: "RSA-PSS",
    saltLength: 32
  },
  keyPair.privateKey,
  data
);

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

Web Crypto API работает исключительно с бинарными данными. Поэтому почти всегда требуется преобразование:

Текст → ArrayBuffer

const encoder = new TextEncoder();
const data = encoder.encode("пример");

ArrayBuffer → Base64 (для передачи)

function toBase64(buffer) {
  const bytes = new Uint8Array(buffer);
  let binary = "";
  bytes.forEach(b => binary += String.fromCharCode(b));
  return btoa(binary);
}

Частые ошибки

1. Неправильный keyUsages

Если ключ не создан с "sign", метод завершится ошибкой:

InvalidAccessError

2. Несовпадение алгоритма

Ключ и алгоритм должны соответствовать друг другу:

  • RSA-PSS ключ нельзя использовать с ECDSA
  • HMAC требует симметричный ключ

3. Неподдерживаемый формат данных

Передаваемые данные должны быть BufferSource, строки недопустимы напрямую.


Связь с verify

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

  • sign — создаёт подпись
  • verify — проверяет подпись
const valid = await crypto.subtle.verify(
  {
    name: "RSA-PSS",
    saltLength: 32
  },
  publicKey,
  signature,
  data
);

Особенности безопасности

  • Приватный ключ никогда не покидает Web Crypto API
  • Операции выполняются в изолированной криптографической подсистеме браузера
  • Невозможно получить «сырой» доступ к ключу, если он не экспортируемый

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

  • HMAC — самый быстрый алгоритм
  • ECDSA — оптимален для мобильных и TLS-сценариев
  • RSA-PSS — медленнее, но широко совместим

Типичные сценарии применения

  • Подпись JWT (частично на клиенте в специфических архитектурах)
  • Проверка целостности загружаемых данных
  • Криптографическая аутентификация запросов API
  • Подпись сообщений в защищённых каналах

Внутреннее представление результата

Возвращаемый ArrayBuffer не имеет универсального текстового формата. Его структура зависит от алгоритма:

  • HMAC → фиксированная длина хэша (например, 32 байта для SHA-256)
  • RSA → big-endian бинарный блок
  • ECDSA → ASN.1 DER структура с двумя числами (r, s)

При необходимости передачи подписи в сеть используется кодирование:

  • Base64
  • Hex
  • Base64URL (для JWT-подобных форматов)