Сообщение об ошибке
Malformed UTF-8 data
CryptoJS — thrown by .toString(CryptoJS.enc.Utf8) after decrypt
Что это означает
CryptoJS расшифровала ваш шифртекст в байты, которые не образуют корректный UTF-8, и преобразовать их в строку не удалось. В AES из CryptoJS (режим CBC) нет проверки целостности, поэтому с неверным ключом расшифрование не завершается ошибкой — получаются просто случайные байты, и первым это замечает декодер UTF-8. Тот же неверный ключ может вместо этой ошибки дать пустую строку (если проверка дополнения по стечению обстоятельств не проходит без исключения), так что у обоих симптомов один диагноз.
Почему это происходит
Неверный ключ или парольная фраза
частоРасшифрование ключом, который отличается от ключа шифрования, даёт псевдослучайный результат. Какой симптом вы получите, зависит от длины сообщения: в нашем тесте эту ошибку выбрасывало примерно каждое восьмое расшифрование с неверным ключом при длине сообщения 29 байт, около половины при 100 байтах и все до одного при 300 байтах; почти все остальные возвращали "" — так или иначе, под подозрением ключ.
Стороны по-разному формируют ключ
частоCryptoJS.AES.encrypt(msg, "passphrase") получает ключ и IV через совместимую с OpenSSL функцию формирования ключа (KDF) со случайной солью. Если другая сторона использует парольную фразу как готовый ключ (или другая платформа расшифровывает без этой KDF), ключи получаются разными, и на выходе мусор.
Шифртекст повреждён при передаче
иногдаBase64, испорченный URL-кодированием (+ превращается в пробел), обрезка при хранении или двойное кодирование меняют байты шифртекста; расшифрование изменённых байтов в режиме CBC даёт мусор без какой-либо ошибки на криптографическом уровне.
Как исправить
- 1.
Работайте с ключом явно и одинаково на обеих сторонах
Исключите неоднозначность с KDF для парольной фразы: передавайте настоящий ключ как WordArray и явный IV, одинаково на обеих сторонах.
js const key = CryptoJS.enc.Hex.parse(hexKey); // 32-byte key, both sides const iv = CryptoJS.enc.Hex.parse(hexIv); // 16-byte IV, both sides const ct = CryptoJS.AES.encrypt(plaintext, key, { iv }); const pt = CryptoJS.AES.decrypt(ct.toString(), key, { iv }) .toString(CryptoJS.enc.Utf8); - 2.
Считайте пустой результат той же ошибкой
Проверяйте результат и выбрасывайте настоящую ошибку — молчаливые пустые строки месяцами скрывают ошибки с неверным ключом.
js const pt = CryptoJS.AES.decrypt(ct, key, { iv }).toString(CryptoJS.enc.Utf8); if (!pt) throw new Error("decryption failed: wrong key/IV or corrupted ciphertext"); - 3.
Выбирайте схему с аутентификацией
AES-GCM в WebCrypto аутентифицирует шифртекст: неверный ключ гарантированно приводит к явной ошибке, а не к мусору на выходе, и этот класс ошибок исчезает полностью.
js const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct); // wrong key → OperationError, never silent garbage