Юнит-тестирование криптографических функций

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

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


Архитектура тестирования SJCL

В экосистеме SJCL тесты строятся вокруг нескольких уровней проверки:

  • корректность математических преобразований
  • соответствие стандартам (AES, SHA-256, HMAC и др.)
  • воспроизводимость результатов на разных платформах JavaScript
  • устойчивость к некорректным входным данным
  • проверка совместимости режимов шифрования

Типичная структура тестового набора включает:

  • тесты низкоуровневых операций (битовые сдвиги, XOR, конвертация слов)
  • тесты примитивов (AES, SHA, PBKDF2)
  • тесты режимов (CBC, GCM, CCM)
  • тесты утилит (кодирование, сериализация, random)

Проверка базовых криптографических примитивов

Хеш-функции

Хеш-функции в SJCL (например SHA-256) проверяются через заранее известные тест-векторы.

const sjcl = require('sjcl');

const input = "hello";
const expected = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";

const hash = sjcl.hash.sha256.hash(input);
const result = sjcl.codec.hex.fromBits(hash);

if (result !== expected) {
    throw new Error("SHA-256 не совпадает с эталоном");
}

Ключевой принцип здесь — использование фиксированных входных данных и строгого сравнения с официальными векторами NIST.


Симметричное шифрование AES

Тестирование AES требует проверки не только шифрования, но и обратного преобразования.

const key = sjcl.codec.hex.toBits("000102030405060708090a0b0c0d0e0f");
const plaintext = sjcl.codec.utf8String.toBits("test data");

const aes = new sjcl.cipher.aes(key);

const ciphertext = aes.encrypt(plaintext);
const decrypted = aes.decrypt(ciphertext);

const decryptedText = sjcl.codec.utf8String.fromBits(decrypted);

if (decryptedText !== "test data") {
    throw new Error("AES decryption failed");
}

Особое внимание уделяется:

  • совпадению результата шифрования с эталонными векторами
  • обратимости операций
  • стабильности при разных длинах ключей (128/192/256 бит)

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

CBC (Cipher Block Chaining)

Режим CBC проверяется на корректную обработку цепочек блоков.

const iv = sjcl.random.randomWords(4);
const mode = new sjcl.mode.cbc(aes);

const enc = mode.encrypt(plaintext, iv);
const dec = mode.decrypt(enc, iv);

Основные тестовые сценарии:

  • идентичность результата при повторной расшифровке
  • чувствительность к изменению IV
  • корректная обработка padding (PKCS#7)

GCM и CCM

Аутентифицированные режимы требуют дополнительной проверки целостности данных.

Проверяется:

  • корректность тега аутентификации
  • обнаружение любых изменений ciphertext
  • отказ при неверном ключе или nonce

Проверка генератора случайных чисел

В SJCL используется криптографически стойкий генератор случайных чисел, основанный на AES-CTR.

Юнит-тесты здесь не проверяют «случайность» напрямую, а фокусируются на:

  • детерминированности при фиксированном seed
  • отсутствии повторов в коротких последовательностях
  • корректной инициализации состояния
sjcl.random.addEntropy([1,2,3,4], 128);

const r1 = sjcl.random.randomWords(4);
const r2 = sjcl.random.randomWords(4);

if (sjcl.codec.hex.fromBits(r1) === sjcl.codec.hex.fromBits(r2)) {
    throw new Error("Повторяющиеся случайные значения");
}

Тестирование PBKDF2

Функция PBKDF2 используется для получения ключей из паролей и требует особой точности.

Основные проверки:

  • соответствие RFC 2898 test vectors
  • корректность числа итераций
  • устойчивость к коротким и длинным паролям
const derived = sjcl.misc.pbkdf2("password", "salt", 1000, 256);
const hex = sjcl.codec.hex.fromBits(derived);

Сравнение проводится с заранее вычисленными значениями.


Негативные тесты

Криптографические библиотеки обязаны корректно обрабатывать ошибочные входные данные.

Проверяются сценарии:

  • null / undefined вход
  • пустые массивы битов
  • неверные ключи
  • поврежденные ciphertext
try {
    aes.decrypt([]);
    throw new Error("Ожидалась ошибка");
} catch (e) {
    // корректное поведение
}

Тестирование совместимости представлений данных

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

  • bitArray
  • hex
  • base64
  • UTF-8

Юнит-тесты проверяют обратимость преобразований:

const bits = sjcl.codec.utf8String.toBits("data");
const base64 = sjcl.codec.base64.fromBits(bits);
const restored = sjcl.codec.utf8String.fromBits(
    sjcl.codec.base64.toBits(base64)
);

if (restored !== "data") {
    throw new Error("Ошибка кодирования/декодирования");
}

Стресс-тестирование граничных значений

Криптографические функции тестируются на:

  • очень длинных входных строках
  • многократных итерациях (10^5+)
  • экстремальных значениях ключей
  • нестандартных длинах блоков

Цель — выявление переполнений, утечек памяти и деградации производительности.


Регрессионное тестирование

Каждое изменение в криптографическом ядре требует повторной проверки всех эталонных векторов.

Типичный подход:

  • фиксированный набор тест-векторов хранится в JSON
  • результаты сравниваются побайтно
  • любое отклонение считается критической ошибкой

Изоляция тестовой среды

Для исключения влияния внешних факторов тесты выполняются в контролируемой среде:

  • фиксированный источник энтропии
  • отключённые сторонние полифиллы
  • единая реализация BigInt/bitwise операций
  • одинаковые версии движков (Node.js / браузер)

Автоматизация выполнения тестов

Обычно используется интеграция с:

  • Mocha
  • Jasmine
  • QUnit

Пример структуры теста:

describe("AES tests", function () {
    it("encrypt/decrypt cycle", function () {
        const aes = new sjcl.cipher.aes(key);
        const enc = aes.encrypt(plaintext);
        const dec = aes.decrypt(enc);

        assert.equal(
            sjcl.codec.hex.fromBits(dec),
            sjcl.codec.hex.fromBits(plaintext)
        );
    });
});

Метрики качества тестирования

В криптографических библиотеках оцениваются:

  • покрытие всех режимов и алгоритмов
  • наличие тест-векторов для каждого стандарта
  • стабильность результатов на разных движках JS
  • отсутствие флаки-тестов (нестабильных результатов)

Особенности тестирования в JavaScript-криптографии

JavaScript накладывает специфические ограничения:

  • отсутствие строгой типизации
  • различия в реализации битовых операций
  • различия в эндианности
  • зависимость от движка (V8, SpiderMonkey, JavaScriptCore)

Поэтому тестирование SJCL ориентировано не только на алгоритмы, но и на поведение в различных средах выполнения.