Отсутствие проверки целостности

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

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

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

Шифрование без аутентификации

Типичный пример использования AES в CryptoJS:

const CryptoJS = require("crypto-js");

const secretKey = "my-secret-key";
const plaintext = "confidential data";

const encrypted = CryptoJS.AES.encrypt(plaintext, secretKey).toString();
const decrypted = CryptoJS.AES.decrypt(encrypted, secretKey)
    .toString(CryptoJS.enc.Utf8);

console.log(encrypted);
console.log(decrypted);

На первый взгляд результат выглядит корректным: данные зашифрованы и восстановлены. Однако структура AES-CBC в CryptoJS не защищает от изменения ciphertext.

Модификация зашифрованных данных

Зашифрованная строка может быть изменена без знания ключа. Даже минимальное изменение одного символа приводит к изменению результата расшифровки:

const altered = encrypted.replace(/.{5}$/, "AAAAA");

const result = CryptoJS.AES.decrypt(altered, secretKey)
    .toString(CryptoJS.enc.Utf8);

console.log(result);

Возможные последствия:

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

Причина заключается в том, что режим CBC обеспечивает только конфиденциальность, но не аутентификацию блоков.

Проблема отсутствия контроля целостности

При использовании только шифрования отсутствует механизм проверки:

  • был ли изменён ciphertext
  • корректен ли источник данных
  • происходила ли подмена отдельных блоков

Это делает систему уязвимой к атакам, связанным с подменой и модификацией зашифрованных сообщений.

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

Роль HMAC в обеспечении целостности

Для защиты данных используется механизм кода аутентификации сообщения (MAC). На практике часто применяется HMAC на основе SHA-256.

Пример формирования защищённой структуры:

const message = "confidential data";
const key = "encryption-key";
const hmacKey = "auth-key";

const encrypted = CryptoJS.AES.encrypt(message, key).toString();

const signature = CryptoJS.HmacSHA256(encrypted, hmacKey).toString();

При расшифровке необходимо выполнить проверку подписи до обработки данных:

const recalculated = CryptoJS.HmacSHA256(encrypted, hmacKey).toString();

if (recalculated !== signature) {
    throw new Error("Integrity check failed");
}

const decrypted = CryptoJS.AES.decrypt(encrypted, key)
    .toString(CryptoJS.enc.Utf8);

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

Схема encrypt-then-MAC

Корректной криптографической практикой считается схема «сначала шифрование, затем MAC»:

  1. Генерация ciphertext
  2. Вычисление HMAC от ciphertext
  3. Передача пары (ciphertext, mac)

При расшифровке:

  • сначала проверяется MAC
  • затем выполняется дешифрование

Любое отклонение MAC делает дальнейшую обработку бессмысленной.

Слабые места типичной реализации в CryptoJS

CryptoJS не предоставляет защищённый режим аутентифицированного шифрования (AEAD) на уровне AES-GCM в стандартном API. Поэтому разработчик вынужден вручную комбинировать алгоритмы.

Это приводит к ряду проблем:

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

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

Поведение при повреждённых данных

При изменении ciphertext:

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

Пример:

try {
    const data = CryptoJS.AES.decrypt(altered, secretKey)
        .toString(CryptoJS.enc.Utf8);

    console.log(data);
} catch (e) {
    console.log("decryption error");
}

Однако отсутствие исключения не означает корректность результата, что создаёт ложное ощущение безопасности.

Причина уязвимости на уровне модели шифрования

Симметричное шифрование в CryptoJS реализует только преобразование данных, не включая проверку источника или структуры сообщения. Любая модификация ciphertext приводит к детерминированным или частично детерминированным изменениям plaintext, что позволяет атакующему влиять на результат расшифровки.

Необходимость внешнего слоя аутентификации

Для безопасного использования CryptoJS требуется добавление внешнего слоя контроля целостности. В противном случае шифрование остаётся односторонним механизмом защиты конфиденциальности без гарантии подлинности данных.

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