Защита от атак повторного воспроизведения

Атака повторного воспроизведения (replay attack) возникает, когда злоумышленник перехватывает корректно сформированное зашифрованное сообщение и отправляет его повторно, не изменяя содержимого. Криптографическая корректность при этом не нарушается: сообщение по-прежнему валидно, подписи и аутентификация проходят проверку. Проблема заключается не в шифровании как таковом, а в отсутствии механизма проверки «свежести» сообщения.

В контексте JavaScript-библиотеки SJCL (Stanford Javascript Crypto Library) это особенно важно, поскольку библиотека предоставляет примитивы шифрования и аутентификации, но не навязывает полную протокольную защиту от повторного воспроизведения.


Почему шифрование само по себе не защищает от повторов

Большинство режимов шифрования в SJCL, таких как CCM (Counter with CBC-MAC), обеспечивают:

  • конфиденциальность данных
  • целостность (через MAC)
  • защиту от модификации сообщения

Однако ни один из этих механизмов не гарантирует уникальность или «новизну» сообщения.

Если атакующий перехватил валидный пакет:

const ciphertext = sjcl.encrypt(password, "transfer=100&to=alice");

он может повторно отправить его серверу, и расшифровка пройдёт успешно:

const plaintext = sjcl.decrypt(password, ciphertext);

Без дополнительной логики система не отличит оригинальную транзакцию от повторной.


Источники проблемы в прикладных системах

На практике replay-атаки возникают в системах, где:

  • нет уникальных идентификаторов сообщений
  • отсутствует контроль времени
  • не ведётся журнал уже обработанных запросов
  • криптография используется как единственный механизм защиты

SJCL обеспечивает криптографический уровень защиты, но не прикладной контекст.


Роль nonce и IV в SJCL

В режиме CCM, который часто используется в SJCL, применяется nonce (одноразовое значение), которое должно быть уникальным для каждого шифрования.

Пример:

const encrypted = sjcl.encrypt(password, data, {
    mode: "ccm",
    iv: sjcl.random.randomWords(4, 0)
});

Ключевое свойство:

  • nonce/IV должен быть уникальным для каждой операции шифрования
  • повтор IV с тем же ключом может привести к криптографической компрометации

Однако важно понимать: уникальный IV не защищает от replay на уровне приложения, если атакующий просто пересылает уже готовый пакет с корректным IV.


Разделение двух уровней защиты

Replay-защита всегда делится на два слоя:

1. Криптографический слой

Обеспечивается SJCL:

  • шифрование (AES)
  • аутентификация (MAC)
  • целостность

2. Протокольный слой

Реализуется разработчиком:

  • защита от повторной отправки
  • контроль времени
  • контроль уникальности сообщений

SJCL не реализует второй слой автоматически.


Использование временных меток

Один из базовых способов защиты — добавление времени в защищаемые данные.

Пример структуры сообщения:

const message = {
    user: "alice",
    action: "transfer",
    amount: 100,
    timestamp: Date.now()
};

const encrypted = sjcl.encrypt(password, JSON.stringify(message));

На стороне расшифровки:

const decrypted = JSON.parse(sjcl.decrypt(password, encrypted));

if (Date.now() - decrypted.timestamp > 60000) {
    throw new Error("Сообщение устарело");
}

Ограничения подхода:

  • требуется синхронизация времени
  • возможны атаки в пределах допустимого окна (replay within window)

Использование nonce на уровне приложения

Более надёжный подход — явное введение уникального идентификатора запроса:

const message = {
    id: crypto.randomUUID(),
    payload: "transfer=100&to=alice"
};

const encrypted = sjcl.encrypt(password, JSON.stringify(message));

На сервере ведётся проверка:

const usedIds = new Set();

function handleMessage(encrypted) {
    const msg = JSON.parse(sjcl.decrypt(password, encrypted));

    if (usedIds.has(msg.id)) {
        throw new Error("Повторное сообщение");
    }

    usedIds.add(msg.id);

    process(msg.payload);
}

Особенность:

  • защита становится состоянием системы
  • требуется хранение истории идентификаторов

Привязка сообщений к контексту

Replay-атаки часто успешны, когда сообщение не привязано к конкретному сеансу.

Можно добавить:

  • sessionId
  • userId
  • device fingerprint
  • одноразовый токен сервера

Пример:

const message = {
    sessionId: currentSession,
    nonce: sjcl.random.randomWords(2),
    data: "sensitive_action"
};

Сервер проверяет соответствие sessionId и nonce.


Ограничения SJCL в контексте replay-защиты

SJCL предоставляет низкоуровневые механизмы:

  • sjcl.encrypt
  • sjcl.decrypt
  • режимы AES (CCM, OCB2 и др.)
  • генерацию случайных значений

Но отсутствуют:

  • управление состоянием сообщений
  • защита от повторной доставки
  • протоколы обмена сообщениями

Это означает, что разработчик обязан самостоятельно проектировать анти-replay логику.


Типичные ошибки при использовании SJCL

1. Полное доверие к шифрованию

// Ошибка: считается, что шифрование решает всё
const secure = sjcl.encrypt(password, data);

2. Отсутствие идентификаторов сообщений

Повторный запрос невозможно отличить от оригинального.

3. Использование статического IV

iv: [0, 0, 0, 0]

Это критическая ошибка, которая разрушает безопасность режима.

4. Отсутствие серверного состояния

Без хранения истории обработанных сообщений replay становится тривиальным.


Практическая архитектура защиты

Надёжная схема с использованием SJCL обычно включает:

  1. Формирование сообщения с метаданными

  2. Генерацию уникального nonce

  3. Шифрование через SJCL

  4. Проверку на сервере:

    • уникальность ID
    • актуальность timestamp
    • соответствие сессии

Пример полного цикла:

const message = {
    id: crypto.randomUUID(),
    timestamp: Date.now(),
    payload: "transfer=100&to=alice"
};

const encrypted = sjcl.encrypt(password, JSON.stringify(message), {
    mode: "ccm",
    iv: sjcl.random.randomWords(4, 0)
});

Сервер:

const msg = JSON.parse(sjcl.decrypt(password, encrypted));

if (usedIds.has(msg.id)) return;
if (Date.now() - msg.timestamp > 60000) return;

usedIds.add(msg.id);
process(msg.payload);

Особенности атак в реальных системах

Replay-атаки часто комбинируются с:

  • перехватом HTTP-трафика (MITM без TLS)
  • повтором API-запросов
  • атакой на мобильные клиенты
  • эксплуатацией кеширования запросов

SJCL защищает только содержимое, но не транспорт и не семантику запроса.


Роль TLS и почему его недостаточно

Даже при использовании HTTPS replay-атака возможна:

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

Криптография канала ≠ защита от повторного исполнения операции.


Принцип «один раз и только один раз»

Корректная защита всегда строится на сочетании:

  • криптографической аутентификации (SJCL)
  • уникальности сообщений (nonce/id)
  • серверного состояния (хранение использованных значений)
  • ограничения времени жизни сообщения

Без любого из этих компонентов replay-атака остаётся возможной.