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

Особенности криптографических библиотек в JavaScript

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

TweetNaCl.js реализует облегчённую версию NaCl (Networking and Cryptography library), ориентированную на постоянное время выполнения и минимизацию поверхности атаки. nacl.js является более ранней JavaScript-реализацией, во многом схожей по API, но отличающейся деталями реализации и обработкой входных данных.

Ключевая особенность таких библиотек — отсутствие «мягких» ошибок. Некорректный ввод не должен приводить к неопределённому поведению или частичному успеху криптографической операции. Именно это делает их важной целью для fuzzing-тестирования.


Роль fuzzing в криптографической устойчивости

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

  • падений (crash)
  • зависаний
  • некорректных результатов
  • утечек через побочные эффекты
  • нарушений инвариантов криптографических операций

Для криптографических функций важны не только ошибки исполнения, но и логические уязвимости. Например:

  • подпись может приниматься для повреждённых данных
  • дешифрование может возвращать предсказуемый результат при некорректном ключе
  • повтор nonce может приводить к восстановлению plaintext

Поверхность атаки в TweetNaCl.js

TweetNaCl.js оперирует несколькими основными примитивами:

  • nacl.box / nacl.box.open (асимметричное шифрование)
  • nacl.sign / nacl.sign.open (подписи)
  • nacl.secretbox / nacl.secretbox.open (симметричное шифрование)
  • генерация ключей и nonce

Каждая из этих функций принимает строго типизированные бинарные данные (обычно Uint8Array). Однако в JavaScript границы типов размыты, и часто возможна передача:

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

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


Типы входных искажений

На практике fuzzing криптографических функций включает несколько классов модификаций входных данных.

1. Длина данных

Изменение длины входных буферов:

  • пустые массивы
  • усечённые сообщения
  • чрезмерно длинные буферы
  • несоответствие длины ключа

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


2. Битовые искажения

Входные данные подвергаются:

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

Цель — проверить стабильность алгоритма при минимальных изменениях входа.


3. Некорректные ключи и nonce

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

  • нулевым ключам
  • повторяющимся nonce
  • ключам с высокой энтропией, но неправильной структурой

Повтор nonce в схемах вроде secretbox или box может приводить к серьёзным криптографическим последствиям, поэтому fuzzing часто специально генерирует коллизии nonce.


4. Неканонические представления

JavaScript позволяет легко создавать неоднозначные представления данных:

  • Uint8Array vs Array
  • строки UTF-16 вместо байтов
  • DataView с разной endianness

Fuzzing проверяет, как библиотека реагирует на такие несоответствия.


Инструменты fuzzing в JavaScript-экосистеме

Для тестирования TweetNaCl.js и nacl.js применяются несколько подходов.

1. Property-based testing

Библиотеки вроде fast-check позволяют задавать свойства криптографических операций:

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

Такие свойства формализуют ожидаемое поведение.


2. Coverage-guided fuzzing

Инструменты вроде AFL++ или libFuzzer могут использоваться через WebAssembly-обёртки. В этом случае JavaScript-код компилируется или адаптируется для исполнения в среде, где возможно отслеживание покрытия кода.

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


3. Pure JavaScript fuzzers

Простейшие реализации используют:

  • случайную генерацию Uint8Array
  • мутацию входных данных
  • итеративные циклы с проверкой исключений

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


Пример структуры fuzzing-теста для secretbox

import nacl from "tweetnacl";

function randomBytes(len) {
  const arr = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    arr[i] = Math.floor(Math.random() * 256);
  }
  return arr;
}

for (let i = 0; i < 100000; i++) {
  const key = randomBytes(32);
  const nonce = randomBytes(24);
  const message = randomBytes(Math.floor(Math.random() * 128));

  try {
    const encrypted = nacl.secretbox(message, nonce, key);
    const decrypted = nacl.secretbox.open(encrypted, nonce, key);

    if (decrypted !== null) {
      const sameLength = decrypted.length === message.length;
      if (!sameLength) {
        throw new Error("Length mismatch");
      }
    }
  } catch (e) {
    // исключения фиксируются как потенциальные дефекты
  }
}

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


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

Fuzzing в криптографии часто опирается на набор формальных инвариантов:

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

Нарушение любого из этих свойств указывает на критическую ошибку реализации.


Специфика JavaScript как среды для fuzzing

JavaScript создаёт дополнительные сложности:

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

Криптографические библиотеки вроде TweetNaCl.js стремятся минимизировать влияние оптимизаций, однако fuzzing может выявить случаи, где оптимизатор меняет поведение при необычных входах.


Типичные классы ошибок, выявляемых fuzzing

В процессе тестирования криптографических функций часто обнаруживаются следующие проблемы:

  • отсутствие проверки длины ключа или nonce
  • некорректная обработка null/undefined
  • возврат частично валидных данных вместо ошибки
  • различия поведения между браузерами
  • зависания при экстремально больших входах
  • утечки через исключения или тайминги

Дифференциальный fuzzing между реализациями

TweetNaCl.js и nacl.js часто сравниваются между собой как эталонные реализации. Дифференциальный fuzzing использует следующий принцип:

  • одинаковый вход подаётся в обе реализации
  • результаты сравниваются
  • любое расхождение фиксируется

Такой подход позволяет выявить:

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

Работа с буферами и представлениями данных

Ключевой источник проблем — преобразование данных:

  • String → Uint8Array
  • ArrayBuffer → Uint8Array
  • Base64 → бинарный массив

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

  • усечённые Base64 строки
  • строки с невалидными символами
  • буферы с неправильным offset и length

Это позволяет выявлять ошибки в слоях сериализации, которые формально не относятся к криптографии, но влияют на безопасность.


Значение постоянного времени выполнения

Одним из требований к TweetNaCl.js является выполнение криптографических операций за постоянное время. Fuzzing может использоваться для проверки косвенных признаков нарушения этого свойства:

  • различия времени выполнения при разных входах
  • зависимость числа операций от содержимого данных

Хотя JavaScript затрудняет точный timing-analysis, статистический fuzzing позволяет выявить аномалии.


Композиция fuzzing с property-based подходом

Наиболее эффективные схемы тестирования комбинируют:

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

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