Промисы в Web Crypto API

Web Crypto API построена вокруг интерфейса SubtleCrypto, который предоставляет криптографические операции в полностью асинхронной форме. Почти все методы crypto.subtle возвращают промисы, что делает модель выполнения принципиально неблокирующей и хорошо совместимой с событийным циклом JavaScript.

Основные операции:

  • генерация ключей (generateKey)
  • шифрование и расшифрование (encrypt, decrypt)
  • подпись и проверка (sign, verify)
  • хеширование (digest)
  • импорт и экспорт ключей (importKey, exportKey)
  • обёртка ключей (wrapKey, unwrapKey)

Каждый из этих методов возвращает Promise, который разрешается в соответствующий результат или отклоняется с ошибкой криптографического уровня.


Почему криптографические операции построены на промисах

Криптографические вычисления могут быть:

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

Синхронное выполнение таких операций привело бы к блокировке UI. Промисная модель решает эту проблему:

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

Базовая форма промисов в SubtleCrypto

Каждый метод crypto.subtle возвращает промис с конкретным типом результата.

Хеширование

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

const hashPromise = crypto.subtle.digest("SHA-256", data);

hashPromise.then(hashBuffer => {
  const hashArray = Array.from(new Uint8Array(hashBuffer));
});

Сигнатура:

digest(algorithm, data) -> Promise<ArrayBuffer>

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

const keyPromise = crypto.subtle.generateKey(
  {
    name: "AES-GCM",
    length: 256
  },
  true,
  ["encrypt", "decrypt"]
);

keyPromise.then(keyPair => {
  // keyPair содержит CryptoKey
});

Сигнатура:

generateKey(algorithm, extractable, keyUsages) -> Promise<CryptoKey>

Использование async/await с Web Crypto API

Промисы Web Crypto API напрямую интегрируются с async/await, что делает код более линейным.

Хеширование через async/await

async function hashMessage(message) {
  const data = new TextEncoder().encode(message);

  const hashBuffer = await crypto.subtle.digest("SHA-256", data);

  return new Uint8Array(hashBuffer);
}

Шифрование с ожиданием результата

async function encryptData(key, data) {
  const encoded = new TextEncoder().encode(data);

  return await crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv: crypto.getRandomValues(new Uint8Array(12))
    },
    key,
    encoded
  );
}

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


Цепочки промисов и композиция операций

Web Crypto API часто используется как набор последовательных преобразований:

  1. хеширование
  2. подпись
  3. шифрование результата

Пример цепочки:

crypto.subtle.digest("SHA-256", data)
  .then(hash => crypto.subtle.sign(
    "HMAC",
    key,
    hash
  ))
  .then(signature => {
    return { signature };
  });

При использовании async/await цепочка становится линейной:

const hash = await crypto.subtle.digest("SHA-256", data);
const signature = await crypto.subtle.sign("HMAC", key, hash);

Параллельное выполнение криптографических операций

Поскольку промисы независимы, операции можно выполнять параллельно через Promise.all.

Пример параллельного хеширования

const messages = [
  "one",
  "two",
  "three"
];

const hashes = await Promise.all(
  messages.map(msg =>
    crypto.subtle.digest("SHA-256", new TextEncoder().encode(msg))
  )
);

Каждый вызов digest выполняется независимо, что позволяет эффективно использовать ресурсы CPU.


Обработка ошибок промисов

Любая криптографическая операция может завершиться ошибкой, возвращаемой через отклонение промиса.

Причины ошибок:

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

Обработка через then/catch

crypto.subtle.decrypt(params, key, data)
  .catch(error => {
    console.error(error.name, error.message);
  });

Обработка через try/catch

try {
  const result = await crypto.subtle.decrypt(params, key, data);
} catch (error) {
  console.error(error);
}

Типичные ошибки:

  • InvalidAccessError
  • DataError
  • OperationError

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

Промисы Web Crypto API подчиняются стандартному циклу микрозадач Jav * aScript:

  • результат не возвращается синхронно
  • даже “быстрые” операции (например, SHA-1) выполняются асинхронно
  • .then() всегда откладывается до завершения текущего стека вызовов
console.log("start");

crypto.subtle.digest("SHA-256", data)
  .then(() => console.log("done"));

console.log("end");

Порядок вывода:

start
end
done

Отсутствие механизма отмены операций

Промисы Web Crypto API не поддерживают отмену через AbortController.

Это означает:

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

Это важное ограничение модели, особенно при массовой обработке данных.


Комбинирование промисов с буферами данных

Поскольку промисы возвращают ArrayBuffer, часто требуется преобразование:

async function toHex(buffer) {
  const bytes = new Uint8Array(buffer);
  return Array.from(bytes)
    .map(b => b.toString(16).padStart(2, "0"))
    .join("");
}

const hash = await crypto.subtle.digest("SHA-256", data);
const hex = await toHex(hash);

Промисная модель отделяет вычисление от представления результата, что требует явной работы с бинарными данными.


Промисы и управление ключами

Операции с ключами также асинхронны:

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

const exported = await crypto.subtle.exportKey("jwk", key);

Каждый этап — отдельный промис, даже если операция концептуально “локальная”.


Последовательность выполнения криптографических операций

Типичный поток Web Crypto API почти всегда представляет собой цепочку промисов:

  • подготовка данных
  • выполнение криптографической операции
  • преобразование результата
  • повторное использование результата в следующем промисе
const encoded = new TextEncoder().encode("data");

const hash = await crypto.subtle.digest("SHA-256", encoded);

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

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

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

Промисы позволяют выбирать стратегию выполнения:

Последовательная модель

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

Параллельная модель

  • используется при независимых данных
  • повышает производительность
const [hash1, hash2] = await Promise.all([
  crypto.subtle.digest("SHA-256", data1),
  crypto.subtle.digest("SHA-256", data2)
]);

Особенности интеграции с движком JavaScript

Промисы Web Crypto API обрабатываются внутри браузерного движка и могут:

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

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