Интеграционное тестирование в криптографических системах отличается от обычных тестов тем, что проверяется не только корректность отдельных функций, но и согласованность всех звеньев цепочки: генерации ключей, шифрования, передачи данных, дешифрования и проверки целостности. В случае использования Stanford JS Crypto Library (SJCL) это особенно важно, поскольку библиотека работает с низкоуровневыми криптографическими примитивами, а ошибка в одном параметре (например, IV или режим шифрования) полностью ломает совместимость между сторонами обмена.
Для интеграционных тестов криптографического обмена важно создать максимально детерминированную среду. Основная проблема криптографии в тестах — случайность. SJCL активно использует генератор случайных чисел, и если его не контролировать, тесты становятся нестабильными.
Ключевые шаги подготовки:
Пример настройки детерминированного RNG:
sjcl.random = new sjcl.prng(10);
// добавляем фиксированную энтропию для тестов
sjcl.random.addEntropy([0, 1, 2, 3], 128, "test");
Важно понимать: в реальной системе такой подход недопустим, но в тестовой среде он обеспечивает воспроизводимость.
Типичный сценарий интеграционного теста включает две стороны:
Обмен строится на симметричном шифровании 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 или криптографии в целом на разных сторонах.
Сценарий теста:
Пример:
// клиент
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). Интеграционные тесты обязаны фиксировать режим, иначе возможны расхождения.
Проверяемые параметры:
Пример проверки:
const encrypted = sjcl.encrypt(key, message);
if (encrypted.mode !== "ccm") {
throw new Error("Unexpected encryption mode");
}
IV (initialization vector) и salt являются критически важными для воспроизводимости тестов.
Ошибки часто возникают из-за:
Для тестов можно фиксировать IV:
const iv = sjcl.random.randomWords(4, 0);
const encrypted = sjcl.encrypt(key, message, { iv: iv });
В реальных системах фиксировать IV нельзя, но в тестах это позволяет проверить совместимость алгоритма без влияния случайности.
Интеграционные тесты должны проверять не только конфиденциальность, но и целостность сообщений.
SJCL может использовать HMAC внутри схем шифрования или отдельно.
Пример:
const hmac = new sjcl.misc.hmac(sjcl.hash.sha256.hash(key));
hmac.update("message");
const tag = hmac.digest();
Проверка:
Чтобы тест был действительно интеграционным, необходимо эмулировать транспорт:
Пример:
function sendOverNetwork(payload) {
return JSON.parse(JSON.stringify(payload));
}
Ошибка, которую важно тестировать:
В системах с SJCL чаще всего встречаются следующие проблемы:
1. Несовпадение параметров шифрования
2. Проблемы сериализации
3. Неправильная работа с кодеками
4. Несовместимость версий SJCL
При обновлении криптографической библиотеки необходимо убедиться, что старые данные остаются расшифровываемыми.
Сценарий:
const oldPayload = loadFixture("old_version_payload.json");
const result = sjcl.decrypt(key, oldPayload);
if (result !== expected) {
throw new Error("Backward compatibility broken");
}
Дополнительный уровень надежности достигается через генерацию случайных сообщений:
Проверка инварианта:
decrypt(encrypt(m)) == m
Для тысяч входов подряд.
Интеграционные тесты должны включать негативные сценарии:
Пример:
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 с фиксированными условиями:
Рекомендуется:
Типовой тест шифрованного обмена включает:
Финальная модель интеграционного теста выглядит как замкнутый цикл:
Любое расхождение на любом этапе рассматривается как критическая ошибка интеграции, а не локальный баг функции.