Атака повторного воспроизведения (replay attack) возникает, когда злоумышленник перехватывает корректно сформированное зашифрованное сообщение и отправляет его повторно, не изменяя содержимого. Криптографическая корректность при этом не нарушается: сообщение по-прежнему валидно, подписи и аутентификация проходят проверку. Проблема заключается не в шифровании как таковом, а в отсутствии механизма проверки «свежести» сообщения.
В контексте JavaScript-библиотеки SJCL (Stanford Javascript Crypto Library) это особенно важно, поскольку библиотека предоставляет примитивы шифрования и аутентификации, но не навязывает полную протокольную защиту от повторного воспроизведения.
Большинство режимов шифрования в SJCL, таких как CCM (Counter with CBC-MAC), обеспечивают:
Однако ни один из этих механизмов не гарантирует уникальность или «новизну» сообщения.
Если атакующий перехватил валидный пакет:
const ciphertext = sjcl.encrypt(password, "transfer=100&to=alice");
он может повторно отправить его серверу, и расшифровка пройдёт успешно:
const plaintext = sjcl.decrypt(password, ciphertext);
Без дополнительной логики система не отличит оригинальную транзакцию от повторной.
На практике replay-атаки возникают в системах, где:
SJCL обеспечивает криптографический уровень защиты, но не прикладной контекст.
В режиме CCM, который часто используется в SJCL, применяется nonce (одноразовое значение), которое должно быть уникальным для каждого шифрования.
Пример:
const encrypted = sjcl.encrypt(password, data, {
mode: "ccm",
iv: sjcl.random.randomWords(4, 0)
});
Однако важно понимать: уникальный IV не защищает от replay на уровне приложения, если атакующий просто пересылает уже готовый пакет с корректным IV.
Replay-защита всегда делится на два слоя:
Обеспечивается SJCL:
Реализуется разработчиком:
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("Сообщение устарело");
}
Более надёжный подход — явное введение уникального идентификатора запроса:
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-атаки часто успешны, когда сообщение не привязано к конкретному сеансу.
Можно добавить:
Пример:
const message = {
sessionId: currentSession,
nonce: sjcl.random.randomWords(2),
data: "sensitive_action"
};
Сервер проверяет соответствие sessionId и nonce.
SJCL предоставляет низкоуровневые механизмы:
sjcl.encryptsjcl.decryptНо отсутствуют:
Это означает, что разработчик обязан самостоятельно проектировать анти-replay логику.
// Ошибка: считается, что шифрование решает всё
const secure = sjcl.encrypt(password, data);
Повторный запрос невозможно отличить от оригинального.
iv: [0, 0, 0, 0]
Это критическая ошибка, которая разрушает безопасность режима.
Без хранения истории обработанных сообщений replay становится тривиальным.
Надёжная схема с использованием SJCL обычно включает:
Формирование сообщения с метаданными
Генерацию уникального nonce
Шифрование через SJCL
Проверку на сервере:
Пример полного цикла:
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-атаки часто комбинируются с:
SJCL защищает только содержимое, но не транспорт и не семантику запроса.
Даже при использовании HTTPS replay-атака возможна:
Криптография канала ≠ защита от повторного исполнения операции.
Корректная защита всегда строится на сочетании:
Без любого из этих компонентов replay-атака остаётся возможной.