Интеграционное тестирование шифрованного обмена

Интеграционное тестирование в криптографических системах отличается от обычных тестов тем, что проверяется не только корректность отдельных функций, но и согласованность всех звеньев цепочки: генерации ключей, шифрования, передачи данных, дешифрования и проверки целостности. В случае использования Stanford JS Crypto Library (SJCL) это особенно важно, поскольку библиотека работает с низкоуровневыми криптографическими примитивами, а ошибка в одном параметре (например, IV или режим шифрования) полностью ломает совместимость между сторонами обмена.


Подготовка тестового окружения

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

Ключевые шаги подготовки:

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

Пример настройки детерминированного RNG:

sjcl.random = new sjcl.prng(10);

// добавляем фиксированную энтропию для тестов
sjcl.random.addEntropy([0, 1, 2, 3], 128, "test");

Важно понимать: в реальной системе такой подход недопустим, но в тестовой среде он обеспечивает воспроизводимость.


Базовая модель шифрованного обмена

Типичный сценарий интеграционного теста включает две стороны:

  • Клиент (JavaScript + SJCL)
  • Сервер (например, Node.js с той же библиотекой или совместимой реализацией)

Обмен строится на симметричном шифровании AES (в SJCL это sjcl.encrypt / sjcl.decrypt).


Шифрование и дешифрование как контракт системы

Интеграционный тест должен проверять не функцию, а контракт:

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

Пример базового сценария:

const key = "secret-key-123";
const message = "confidential payload";

const encrypted = sjcl.encrypt(key, message);
const decrypted = sjcl.decrypt(key, encrypted);

if (decrypted !== message) {
  throw new Error("Mismatch in encryption pipeline");
}

На уровне интеграции важно проверять не только результат, но и совместимость формата JSON, который возвращает SJCL:

{
  "iv": "...",
  "v": 1,
  "iter": 10000,
  "ks": 128,
  "ts": 64,
  "mode": "ccm",
  "adata": "",
  "cipher": "aes",
  "salt": "...",
  "ct": "..."
}

Любое изменение структуры между версиями клиента и сервера приводит к несовместимости.


Проверка совместимости клиент-сервер

Основная сложность интеграционного тестирования — разные реализации SJCL или криптографии в целом на разных сторонах.

Сценарий теста:

  1. клиент шифрует сообщение
  2. сервер расшифровывает
  3. сервер шифрует ответ
  4. клиент расшифровывает

Пример:

// клиент
const encrypted = sjcl.encrypt(key, "ping");

// сервер (эмуляция)
const decrypted = sjcl.decrypt(key, encrypted);
const response = sjcl.encrypt(key, decrypted + " pong");

// клиент
const final = sjcl.decrypt(key, response);

Ожидаемый результат:

ping pong

Работа с режимами шифрования

SJCL поддерживает различные режимы (например, CCM). Интеграционные тесты обязаны фиксировать режим, иначе возможны расхождения.

Проверяемые параметры:

  • mode (ccm, ocb2)
  • iteration count (iter)
  • key size (ks)
  • tag size (ts)

Пример проверки:

const encrypted = sjcl.encrypt(key, message);

if (encrypted.mode !== "ccm") {
  throw new Error("Unexpected encryption mode");
}

Контроль IV и соли

IV (initialization vector) и salt являются критически важными для воспроизводимости тестов.

Ошибки часто возникают из-за:

  • различий генерации IV между средами
  • некорректной сериализации JSON
  • потери байтов при транспортировке

Для тестов можно фиксировать IV:

const iv = sjcl.random.randomWords(4, 0);

const encrypted = sjcl.encrypt(key, message, { iv: iv });

В реальных системах фиксировать IV нельзя, но в тестах это позволяет проверить совместимость алгоритма без влияния случайности.


Проверка целостности данных (HMAC)

Интеграционные тесты должны проверять не только конфиденциальность, но и целостность сообщений.

SJCL может использовать HMAC внутри схем шифрования или отдельно.

Пример:

const hmac = new sjcl.misc.hmac(sjcl.hash.sha256.hash(key));

hmac.update("message");
const tag = hmac.digest();

Проверка:

  • одинаковый вход → одинаковый MAC
  • изменение байта → изменение результата

Симуляция сетевого слоя

Чтобы тест был действительно интеграционным, необходимо эмулировать транспорт:

  • JSON сериализация
  • HTTP слой или WebSocket
  • возможные потери/изменения данных

Пример:

function sendOverNetwork(payload) {
  return JSON.parse(JSON.stringify(payload));
}

Ошибка, которую важно тестировать:

  • потеря типов (Uint8Array → string)
  • повреждение base64
  • неправильная кодировка UTF-8

Типичные ошибки интеграции

В системах с SJCL чаще всего встречаются следующие проблемы:

1. Несовпадение параметров шифрования

  • разные iter или ks между клиентом и сервером

2. Проблемы сериализации

  • JSON.stringify ломает bitArray без конвертации

3. Неправильная работа с кодеками

  • отсутствует sjcl.codec.base64

4. Несовместимость версий SJCL

  • изменения формата ciphertext

Тестирование обратной совместимости

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

Сценарий:

  • зашифровать сообщение старой версией
  • расшифровать новой
  • проверить идентичность результата
const oldPayload = loadFixture("old_version_payload.json");
const result = sjcl.decrypt(key, oldPayload);

if (result !== expected) {
  throw new Error("Backward compatibility broken");
}

Property-based тестирование криптографического обмена

Дополнительный уровень надежности достигается через генерацию случайных сообщений:

  • произвольные строки
  • бинарные данные
  • длинные payload’ы

Проверка инварианта:

decrypt(encrypt(m)) == m

Для тысяч входов подряд.


Проверка устойчивости к повреждению данных

Интеграционные тесты должны включать негативные сценарии:

  • изменение одного символа ciphertext
  • удаление части JSON
  • подмена IV

Пример:

let tampered = JSON.parse(JSON.stringify(encrypted));
tampered.ct = tampered.ct.slice(1);

try {
  sjcl.decrypt(key, tampered);
  throw new Error("Decryption should have failed");
} catch (e) {
  // expected
}

Интеграция в CI пайплайн

Криптографические тесты должны выполняться в CI с фиксированными условиями:

  • одинаковая версия Node.js
  • отключённый entropy fallback
  • фиксированные тестовые ключи
  • отсутствие параллельных гонок RNG

Рекомендуется:

  • запускать тесты в изолированном окружении
  • запрещать использование системного RNG
  • фиксировать seed генератора

Структура полноценного интеграционного теста

Типовой тест шифрованного обмена включает:

  1. генерацию ключа
  2. шифрование сообщения
  3. передачу через имитацию сети
  4. дешифрование на другой стороне
  5. проверку результата
  6. проверку метаданных (mode, iv, salt)
  7. негативные сценарии повреждения

Проверка согласованности полного цикла обмена

Финальная модель интеграционного теста выглядит как замкнутый цикл:

  • клиент → шифрование → сеть → сервер → дешифрование → обработка → шифрование → сеть → клиент → дешифрование

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