Сообщение об ошибке
OperationError: The operation failed for an operation-specific reason
WebCrypto crypto.subtle — browsers & Node
Что это означает
Намеренно расплывчатая универсальная ошибка WebCrypto: сбой произошёл в самом примитиве. Для decrypt() с AES-GCM она означает, что тег аутентификации не прошёл проверку: неверный ключ, неверный IV, неверные дополнительные данные или изменённый шифртекст. Спецификация оставляет сообщение общим, чтобы реализации не раскрывали, почему криптографическая проверка не прошла. Для sign() и deriveBits() то же имя охватывает внутренние сбои примитива, поэтому операция в трассировке стека важнее текста сообщения.
Почему это происходит
Сбой аутентификации AES-GCM
частоРасшифрование другим ключом, чем тот, которым шифровали, несовпадающий IV, отсутствующие или другие additionalData либо шифртекст, который при передаче потерял добавленный в конец тег длиной 16 байт. GCM — режим с аутентификацией: в любом из этих случаев вся операция завершается ошибкой, а не возвращает мусор.
Шифртекст обрезан или перекодирован
частоBase64 обрабатывается непоследовательно (алфавит URL-safe в одном месте и стандартный в другом), столбец базы данных обрезает байты или преобразования между строками и двоичными данными портят буфер. Проверка тега обнаруживает любое такое изменение — в этом её задача.
Параметры формирования ключа вне допустимого диапазона
иногдаderiveBits/deriveKey с невыполнимой длиной, PBKDF2 с нулём итераций или вычисление ECDH на несогласованных входных данных в ряде сред выполнения проявляются как OperationError.
Как исправить
- 1.
Передавайте IV вместе с шифртекстом и декодируйте симметрично
Добавляйте IV в начало шифртекста и отделяйте его при расшифровании, чтобы стороны никогда не расходились; везде используйте один вариант base64.
js // encrypt const iv = crypto.getRandomValues(new Uint8Array(12)); const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, data); const packed = new Uint8Array([...iv, ...new Uint8Array(ct)]); // decrypt const buf = packedFromWire; const pt = await crypto.subtle.decrypt( { name: "AES-GCM", iv: buf.slice(0, 12) }, key, buf.slice(12), ); - 2.
Считайте это вердиктом о целостности, а не повреждением данных
Если decrypt выбрасывает OperationError, правильный ответ — «этот шифртекст не подлинный для этого ключа»: отклоните его. Не повторяйте попытку, подбирая дополнение, и не переходите на режимы без аутентификации: то, что проверка не прошла, и есть защита.
- 3.
Сравните отпечатки ключей на обеих сторонах
Экспортируйте оба ключа и вычислите их хеши; если дайджесты различаются, загадка решена за один шаг.
js const raw = await crypto.subtle.exportKey("raw", key); const fp = await crypto.subtle.digest("SHA-256", raw); console.log(Buffer.from(fp).toString("hex").slice(0, 16)); // compare on both sides