Принципы тестирования криптографии

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

Ключевым требованием становится детерминированность результата для одних и тех же входных данных. Любое отклонение от повторяемости сигнализирует о нарушении модели работы алгоритма или некорректной обработке состояния (например, IV, salt или кодировок).


Эталонные векторы как основа проверки

Классический подход к тестированию криптографических функций основан на использовании известных тестовых векторов (test vectors), предоставляемых стандартами NIST, RFC и спецификациями алгоритмов.

Для CryptoJS это особенно важно, поскольку библиотека реализует алгоритмы на уровне JavaScript, где отсутствует аппаратная гарантия корректности криптографических примитивов.

Пример проверки SHA-256:

const CryptoJS = require("crypto-js");

const input = "hello";
const hash = CryptoJS.SHA256(input).toString();

console.log(hash);

Сравнение результата должно выполняться с эталонным значением:

2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

Любое расхождение указывает либо на ошибку реализации, либо на некорректное преобразование строки (например, различие UTF-8 и UTF-16 представления).


Контроль кодировок и нормализация входных данных

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

CryptoJS оперирует внутренним форматом WordArray, поэтому строка перед обработкой должна быть строго определена:

  • UTF-8 интерпретация
  • отсутствие скрытых символов
  • отсутствие нормализационных различий (NFC/NFD)

Пример явного задания кодировки:

const msg = CryptoJS.enc.Utf8.parse("тестовое сообщение");
const hash = CryptoJS.SHA256(msg).toString();

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

  • кириллицу
  • эмодзи (в контексте UTF-16 суррогатов)
  • строки с нулевыми байтами
  • очень длинные входы

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

AES в CryptoJS является одним из наиболее используемых алгоритмов, однако корректность его работы зависит от нескольких факторов:

  • режим шифрования (CBC, ECB, CFB)
  • наличие и корректность IV
  • правильность padding
  • согласованность ключа

Пример базового теста AES:

const key = CryptoJS.enc.Utf8.parse("1234567890123456");
const iv = CryptoJS.enc.Utf8.parse("1234567890123456");

const encrypted = CryptoJS.AES.encrypt("secret", key, { iv: iv });
const decrypted = CryptoJS.AES.decrypt(encrypted, key, { iv: iv });

console.log(decrypted.toString(CryptoJS.enc.Utf8));

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

  • обратимости (encrypt → decrypt → исходное значение)
  • стабильности результата при одинаковых входных данных
  • различия ciphertext при изменении IV
  • идентичности результата с эталонными реализациями (OpenSSL, WebCrypto)

Проверка свойств IV и nonce

В режимах CBC и GCM (если эмулируется) IV должен удовлетворять условию уникальности. Тестирование включает проверку того, что:

  • одинаковый plaintext + одинаковый key + разный IV → разные ciphertext
  • повтор IV приводит к предсказуемости результата (что является критической ошибкой)
const key = CryptoJS.enc.Utf8.parse("1234567890123456");

const enc1 = CryptoJS.AES.encrypt("data", key, { iv: CryptoJS.enc.Utf8.parse("1111111111111111") });
const enc2 = CryptoJS.AES.encrypt("data", key, { iv: CryptoJS.enc.Utf8.parse("2222222222222222") });

console.log(enc1.toString() !== enc2.toString());

Тестирование хэш-функций и устойчивость к коллизиям

Хотя проверка отсутствия коллизий невозможна в практическом смысле, тестирование охватывает:

  • стабильность результата
  • одинаковость хэша для одинаковых входов
  • изменение одного байта входа → радикальное изменение выхода (эффект лавины)
const h1 = CryptoJS.SHA256("test").toString();
const h2 = CryptoJS.SHA256("test ").toString();

console.log(h1 === h2);

Ожидаемое поведение — строгое различие.


HMAC как проверяемая конструкция

HMAC добавляет слой ключевой зависимости, что требует тестирования:

  • корректность привязки ключа
  • неизменность результата при одинаковом ключе и сообщении
  • изменение ключа полностью меняет результат
const hmac = CryptoJS.HmacSHA256("message", "key").toString();

Проверка включает сопоставление с RFC 4231 test vectors.


Свойства обратимости и идемпотентности

Для симметричных алгоритмов критично:

  • decrypt(encrypt(x)) = x
  • повторное шифрование с одинаковыми параметрами должно давать стабильный результат при фиксированном IV

Любое нарушение указывает на:

  • некорректный padding
  • потерю байтов при конвертации WordArray
  • ошибку кодировки строки

Граничные случаи входных данных

Тестирование криптографии требует проверки нестандартных входов:

  • пустая строка
  • строка длиной 1 байт
  • очень длинные строки (MB и более)
  • бинарные данные (не текст)
  • null-байты внутри строки
const empty = CryptoJS.SHA256("").toString();
const long = CryptoJS.SHA256("a".repeat(1000000)).toString();

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

CryptoJS использует несколько форматов:

  • Hex
  • Base64
  • Latin1
  • Utf8

Ошибки часто возникают при несогласованном преобразовании:

const wordArray = CryptoJS.enc.Utf8.parse("data");
const base64 = CryptoJS.enc.Base64.stringify(wordArray);
const restored = CryptoJS.enc.Base64.parse(base64);

Тестирование требует проверки:

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

Фаззинг криптографических функций

Фаззинг применяется для выявления нестандартных ошибок:

  • случайные строки
  • случайные бинарные массивы
  • случайные комбинации ключей и IV

Цель — выявление:

  • падений
  • некорректных значений (NaN, undefined)
  • нестабильности памяти (в JS проявляется как логическая неконсистентность)

Свойство лавинного эффекта

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

Проверка реализуется сравнением битовых различий:

const a = CryptoJS.SHA256("abc").toString();
const b = CryptoJS.SHA256("abd").toString();

Далее анализируется расстояние Хэмминга между результатами (в тестах часто переводится в бинарный формат).


Проверка устойчивости к неправильным параметрам

Критично тестировать поведение при ошибочных данных:

  • неверный ключ
  • отсутствующий IV
  • некорректная длина ключа
  • строковые значения вместо WordArray

Ожидаемое поведение — либо корректная обработка, либо предсказуемая ошибка без генерации неконсистентного результата.


Сравнение с внешними реализациями

Одним из ключевых методов тестирования является сверка результатов CryptoJS с:

  • OpenSSL
  • WebCrypto API
  • Python cryptography library

Несовпадение даже в одном байте считается критическим отклонением и требует анализа:

  • различий padding
  • различий encoding
  • различий режима шифрования