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

Одной из самых частых проблем становится восприятие crypto.subtle как синхронного или «почти синхронного» API. Все криптографические операции в Web Crypto API возвращают Promise, и это принципиально влияет на архитектуру кода.

Типичная ошибка — попытка использовать результат напрямую:

const hash = crypto.subtle.digest("SHA-256", data);
console.log(hash); // Promise, а не результат

Правильная работа требует ожидания выполнения:

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

Игнорирование этого приводит к цепочке скрытых ошибок, особенно в сложных пайплайнах обработки данных.


Использование неподдерживаемых или устаревших алгоритмов

Web Crypto API поддерживает строго ограниченный набор алгоритмов. Попытка использовать произвольные хеш-функции или шифры приводит к исключениям или невозможности реализации:

  • MD5 — отсутствует полностью
  • SHA-1 — разрешён только для legacy-операций, но считается небезопасным
  • кастомные реализации AES или RSA — невозможны

Частая ошибка — перенос серверной криптографии (Node.js crypto) без адаптации под браузерный контекст.

// Ошибка: алгоритм не поддерживается
crypto.subtle.digest("MD5", data);

Повторное использование IV в AES-GCM

Критическая ошибка безопасности — повторное использование вектора инициализации (IV) при AES-GCM.

const iv = new Uint8Array(12); // фиксированный IV — ошибка
crypto.getRandomValues(iv);

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

Правильный подход — генерация уникального IV для каждого сообщения:

const iv = crypto.getRandomValues(new Uint8Array(12));

Использование Math.random вместо криптографически стойкой генерации

Распространённая ошибка — применение Math.random() для криптографических целей.

const key = Math.random().toString(36);

Этот подход полностью нарушает требования безопасности. В Web Crypto API существует специализированный механизм:

const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);

Ошибки при работе с форматами ключей

Web Crypto API строго различает форматы ключей:

  • raw
  • jwk
  • spki
  • pkcs8

Неверный формат приводит к невозможности импорта или экспорта ключа.

await crypto.subtle.importKey(
  "raw",
  keyData,
  { name: "AES-GCM" },
  false,
  ["encrypt"]
);

Частая ошибка — попытка импортировать PEM-строку напрямую без декодирования base64 и преобразования в ArrayBuffer.


Непонимание работы ArrayBuffer и строк

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

Ошибочный вариант:

crypto.subtle.digest("SHA-256", "text");

Правильный:

const data = new TextEncoder().encode("text");
await crypto.subtle.digest("SHA-256", data);

Игнорирование TextEncoder и TextDecoder

Часто разработчики вручную пытаются преобразовывать строки в байты через charCodeAt, что приводит к ошибкам кодировки.

// Ошибочный подход
const bytes = "test".split("").map(c => c.charCodeAt(0));

Корректный механизм:

const bytes = new TextEncoder().encode("test");

Неправильная обработка ошибок Promise

Ошибки криптографических операций часто остаются необработанными, что приводит к «тихим» сбоям.

crypto.subtle.decrypt(...).then(result => {
  // обработка
});

Отсутствие catch делает отладку практически невозможной:

crypto.subtle.decrypt(...).catch(err => {
  console.error(err);
});

Попытка использования Web Crypto вне secure context

API доступно только в защищённом контексте:

  • HTTPS
  • localhost

Попытка выполнения в HTTP приводит к undefined:

console.log(window.crypto); // может быть недоступно

Неправильное использование PBKDF2

Одна из распространённых ошибок — использование слишком малого числа итераций или статической соли.

const salt = new Uint8Array(8); // недостаточно и часто фиксированная

Итерации ниже сотен тысяч делают алгоритм уязвимым к перебору.


Использование digest вместо password hashing

Web Crypto API предоставляет digest, но он не предназначен для хранения паролей.

crypto.subtle.digest("SHA-256", password);

Это создаёт ложное ощущение безопасности. Для паролей требуется PBKDF2:

await crypto.subtle.deriveKey(
  {
    name: "PBKDF2",
    salt,
    iterations: 100000,
    hash: "SHA-256"
  },
  baseKey,
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt"]
);

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

Частая архитектурная ошибка — хранение криптографических ключей в localStorage.

localStorage.setItem("key", exportedKey);

Это делает ключи доступными для XSS-атак и полностью нивелирует смысл криптографии на стороне клиента.


Путаница между encrypt/decrypt и sign/verify

Разработчики часто смешивают симметричное шифрование и цифровую подпись.

  • encrypt / decrypt — конфиденциальность
  • sign / verify — целостность и аутентификация

Попытка использовать AES для проверки подлинности данных приводит к архитектурным ошибкам безопасности.


Ошибки при работе с wrapKey и unwrapKey

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

crypto.subtle.wrapKey(...)

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


Игнорирование длины ключей и параметров алгоритма

Некорректные параметры приводят к исключениям или снижению стойкости:

  • AES-GCM: 128/192/256 бит
  • RSA-OAEP: минимальная длина 2048 бит

Ошибкой является использование произвольных значений без проверки поддержки браузером.


Неправильное использование Web Crypto в основном потоке

Криптографические операции могут быть тяжёлыми, особенно RSA и PBKDF2. Выполнение их в main thread приводит к фризам интерфейса.

Часто игнорируется возможность использования Web Workers, несмотря на полную поддержку crypto.subtle внутри них.


Путаница между Node.js Crypto и Web Crypto API

Несмотря на схожесть API, различия существенны:

  • разные типы буферов (Buffer vs ArrayBuffer)
  • разные методы импорта ключей
  • различия в поддержке алгоритмов

Код, перенесённый напрямую, часто ломается без адаптации слоя преобразования данных.