Web Crypto API (через crypto.subtle) работает иначе, чем
большинство синхронных JavaScript-библиотек: почти все операции являются
асинхронными и возвращают Promise. Это напрямую влияет на
модель ошибок — вместо привычных throw используются
отклонённые промисы с объектами DOMException.
Ошибки в криптографических операциях здесь не случайность, а часть контрактов API: неправильные ключи, неподдерживаемые алгоритмы, неверные параметры или попытка использовать ключ не по назначению всегда приводят к строго определённым исключениям.
В Web Crypto API важно разделять два класса проблем:
Возникают до выполнения криптографической операции, ещё на этапе проверки входных данных JavaScript-движком:
undefined вместо ArrayBufferТакие ошибки выбрасываются через throw, поэтому их можно
поймать только через try/catch:
try {
crypto.subtle.encrypt("AES-GCM", key, data); // ошибка: неправильный вызов
} catch (e) {
console.log(e.name); // TypeError
}
Возникают уже внутри криптографического ядра браузера и возвращаются
через отклонённый Promise:
crypto.subtle.encrypt(algorithm, key, data)
.catch(err => {
console.log(err.name);
});
Типичные значения err.name:
OperationErrorInvalidAccessErrorDataErrorNotSupportedErrorНаиболее общий тип ошибки, возникающий при невозможности выполнить криптографическую операцию.
Причины:
Пример:
await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
ciphertext
);
Если iv или key не совпадают с теми, что
использовались при шифровании, будет OperationError.
Возникает, когда данные имеют неправильный формат или не соответствуют ожиданиям алгоритма.
Частые причины:
ArrayBufferПример:
await crypto.subtle.importKey(
"raw",
new Uint8Array([1, 2, 3]), // слишком короткий ключ
{ name: "AES-GCM" },
false,
["encrypt"]
);
Возникает при попытке использовать ключ не по назначению.
Например:
encrypt, но используется для
шифрованияПример:
await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
keyWithoutEncryptUsage,
data
);
Возникает, если:
Пример:
crypto.subtle.generateKey(
{ name: "AES-XYZ", length: 256 }, // несуществующий алгоритм
true,
["encrypt"]
);
На практике наиболее читаемый способ работы — использование
async/await с try/catch.
async function encryptData(key, data, iv) {
try {
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
data
);
return encrypted;
} catch (err) {
console.log("Ошибка:", err.name);
if (err.name === "OperationError") {
// например, неверный ключ или повреждённые данные
}
throw err;
}
}
Особенность Web Crypto API: почти все ошибки приходят как
DOMException, а не стандартные Error. Поэтому
важно ориентироваться именно на name, а не на
message.
Альтернативный стиль — классическая цепочка
.then().catch():
crypto.subtle.decrypt(algorithm, key, data)
.then(result => {
console.log("Успешно расшифровано");
})
.catch(err => {
if (err.name === "OperationError") {
console.log("Ошибка расшифровки");
}
});
Такой подход хуже масштабируется при сложных цепочках криптографических операций, но полезен в простых сценариях.
Ключи — основной источник проблем в Web Crypto API.
1. Неправильные usage-флаги
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt"] // нет "decrypt"
);
await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
data
);
Результат: InvalidAccessError
2. Попытка повторного использования ключа в неподдерживаемом контексте
Некоторые ключи нельзя экспортировать или использовать повторно в другом алгоритме.
3. Ошибки импорта ключей
await crypto.subtle.importKey(
"raw",
new Uint8Array([]), // пустой ключ
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
Результат: DataError
AES-GCM — один из самых чувствительных алгоритмов в контексте ошибок.
Критические параметры:
iv (инициализационный вектор)await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: new Uint8Array(12) },
key,
data
);
iv → логическая ошибка
безопасности (не всегда исключение)iv →
OperationErrorOperationErrorВажно: Web Crypto API не всегда явно сигнализирует о криптографически опасных, но технически допустимых операциях (например, повторный IV может не вызвать ошибку).
Ошибки в Web Crypto API не являются стандартными Error.
Они имеют структуру DOMException:
catch (err) {
console.log(err.name); // тип ошибки
console.log(err.message); // описание (не всегда полезно)
console.log(err.code); // устаревшее поле, почти не используется
}
Ключевая особенность — стабильность name. Именно на него
следует опираться в логике обработки.
В реальных приложениях используется централизованный обработчик:
function handleCryptoError(err) {
switch (err.name) {
case "OperationError":
console.log("Криптографическая операция не выполнена");
break;
case "InvalidAccessError":
console.log("Неправильное использование ключа");
break;
case "DataError":
console.log("Некорректные данные");
break;
case "NotSupportedError":
console.log("Алгоритм не поддерживается");
break;
default:
console.log("Неизвестная ошибка");
}
}
Использование:
try {
await crypto.subtle.encrypt(algorithm, key, data);
} catch (err) {
handleCryptoError(err);
}
При создании ключей через generateKey также возможны
сбои:
await crypto.subtle.generateKey(
{ name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]) },
true,
["encrypt", "decrypt"]
);
NotSupportedErrorDataErrorOperationErrorЭкспорт:
await crypto.subtle.exportKey("raw", key);
Если ключ не помечен как экспортируемый:
InvalidAccessErrorИмпорт:
await crypto.subtle.importKey(
"spki",
keyData,
algorithm,
true,
["verify"]
);
Ошибки:
DataErrorInvalidAccessErrorВ отличие от обычного JavaScript, Web Crypto API не даёт детализированных стеков криптографических ошибок.
Поэтому диагностика строится на:
algorithm, key.usages,
iv, длины буферовПример защитного слоя:
function assertBuffer(data) {
if (!(data instanceof ArrayBuffer)) {
throw new TypeError("Ожидается ArrayBuffer");
}
}
Некоторые проблемы не приводят к исключениям сразу:
Такие ошибки проявляются только на этапе дешифрования через
OperationError.
Хотя спецификация стандартизирует DOMException,
поведение может немного отличаться:
messageOperationError vs
DataErrorПоэтому логика приложения не должна зависеть от текста ошибки.
Криптографические операции должны проектироваться с учётом того, что:
Promise rejectionОсновной принцип обработки — работа через name, а не
через текст ошибки, и обязательная проверка данных до вызова
crypto.subtle.