Хэширование произвольных данных

В библиотеке SJCL (Stanford JavaScript Crypto Library) хэширование реализовано через набор криптографических алгоритмов, наиболее часто используемых в веб-приложениях: SHA-256, SHA-1, SHA-512. Основной принцип работы хэш-функций — преобразование произвольного объёма данных в фиксированную строку (digest), обладающую свойствами необратимости и детерминированности.

В SJCL хэш-функции находятся в пространстве sjcl.hash:

sjcl.hash.sha256
sjcl.hash.sha1
sjcl.hash.sha512

Каждый алгоритм предоставляет унифицированный интерфейс, основанный на работе с внутренним представлением данных — bitArray.


Представление данных: bitArray

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

Пример создания bitArray:

const bits = sjcl.codec.utf8String.toBits("test");

Преобразование обратно:

const text = sjcl.codec.utf8String.fromBits(bits);

Внутренне bitArray хранит данные в виде 32-битных слов с дополнительной информацией о длине последнего слова.


Хэширование строк

Самый простой сценарий — получение хэша строки. Для этого используется метод hash у конкретного алгоритма.

const hashBits = sjcl.hash.sha256.hash("hello world");
const hashHex = sjcl.codec.hex.fromBits(hashBits);

В данном примере:

  • строка автоматически интерпретируется как UTF-8 через кодеки SJCL
  • результат возвращается в виде bitArray
  • преобразование в hex выполняется отдельно

Эквивалент более явного варианта:

const data = sjcl.codec.utf8String.toBits("hello world");
const hashBits = sjcl.hash.sha256.hash(data);

Кодеки sjcl

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

  • sjcl.codec.utf8String
  • sjcl.codec.hex
  • sjcl.codec.base64

Пример преобразования хэша в base64:

const hashBits = sjcl.hash.sha256.hash("data");
const hashBase64 = sjcl.codec.base64.fromBits(hashBits);

Хэширование произвольных структур (JSON, объекты)

SJCL не поддерживает прямое хэширование JavaScript-объектов. Любая структура должна быть сериализована.

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

const obj = {
  user: "alice",
  id: 42,
  roles: ["admin", "editor"]
};

const json = JSON.stringify(obj);
const bits = sjcl.codec.utf8String.toBits(json);
const hash = sjcl.hash.sha256.hash(bits);

Ключевая проблема здесь — детерминированность сериализации. Разные порядки ключей приведут к разным хэшам, поэтому для стабильного результата требуется нормализация объекта.


Инкрементальное хэширование больших данных

SJCL поддерживает потоковое хэширование через методы update и finalize. Это важно при работе с большими файлами или потоками данных.

Пример:

const hasher = new sjcl.hash.sha256();

hasher.update(sjcl.codec.utf8String.toBits("part1 "));
hasher.update(sjcl.codec.utf8String.toBits("part2"));

const result = hasher.finalize();
const hex = sjcl.codec.hex.fromBits(result);

Особенности:

  • update можно вызывать многократно
  • finalize завершает вычисление и возвращает результат
  • после finalize экземпляр хэшера нельзя использовать повторно

Работа с бинарными данными (ArrayBuffer, Uint8Array)

В веб-приложениях данные часто приходят в виде ArrayBuffer или Uint8Array. SJCL требует преобразования в bitArray.

Пример конвертации Uint8Array:

function bytesToBits(bytes) {
  const words = [];
  let acc = 0;
  let count = 0;

  for (let i = 0; i < bytes.length; i++) {
    acc = (acc << 8) | bytes[i];
    count += 8;

    if (count === 32) {
      words.push(acc);
      acc = 0;
      count = 0;
    }
  }

  if (count > 0) {
    acc = acc << (32 - count);
    words.push(acc | count);
  }

  return words;
}

Использование:

const buffer = new Uint8Array([1, 2, 3, 4]);
const bits = bytesToBits(buffer);
const hash = sjcl.hash.sha256.hash(bits);

На практике чаще используется sjcl.codec.hex или сторонние утилиты для конвертации, так как SJCL не предоставляет прямого удобного адаптера для ArrayBuffer.


Хэширование файлов в браузере

При обработке файлов через <input type="file"> используется FileReader:

const file = input.files[0];
const reader = new FileReader();

reader.onl oad = function () {
  const arrayBuffer = reader.result;
  const bytes = new Uint8Array(arrayBuffer);

  const bits = bytesToBits(bytes);
  const hashBits = sjcl.hash.sha256.hash(bits);
  const hashHex = sjcl.codec.hex.fromBits(hashBits);
};

reader.readAsArrayBuffer(file);

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


Объединение данных перед хэшированием

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

Правильный подход:

const h = new sjcl.hash.sha256();

h.update(sjcl.codec.utf8String.toBits("header"));
h.update(sjcl.codec.utf8String.toBits("|"));
h.update(sjcl.codec.utf8String.toBits("payload"));

const digest = h.finalize();

Неправильный подход — конкатенация строк до кодирования, если данные бинарные или структурированные, так как это может привести к неоднозначности.


Типичные ошибки при хэшировании в SJCL

1. Работа со строками без кодеков

sjcl.hash.sha256.hash("text"); // не гарантирует корректное преобразование

Корректный вариант:

sjcl.hash.sha256.hash(sjcl.codec.utf8String.toBits("text"));

2. Повторное использование хэшера после finalize

const h = new sjcl.hash.sha256();
h.update(...);
h.finalize();
h.update(...); // ошибка логики

3. Нестабильная сериализация объектов

JSON.stringify(obj)

Без контроля порядка ключей результат может отличаться между окружениями.


4. Потеря бинарных данных при использовании UTF-8

Использование utf8String для произвольных байтов приводит к искажению данных.


Использование хэширования как строительного блока

Хэширование в SJCL часто используется не само по себе, а как основа для:

  • проверки целостности данных
  • идентификаторов сообщений
  • кеширования результатов вычислений
  • построения HMAC (в связке с sjcl.misc.hmac)
  • цифровых отпечатков структур данных

Типичный паттерн идентификатора:

const id = sjcl.codec.hex.fromBits(
  sjcl.hash.sha256.hash(sjcl.codec.utf8String.toBits(json))
);