SHA-3 и семейство Keccak в SJCL

Криптографическое семейство SHA-3 (Keccak) основано на принципиально иной конструкции хеш-функций по сравнению с SHA-2. Вместо классической схемы Меркла–Дамгора используется конструкция sponge construction (губка), в которой входные данные “впитываются” в внутреннее состояние фиксированного размера, после чего из него “выжимаются” выходные биты требуемой длины.

В контексте JavaScript-библиотеки Stanford JS Crypto Library (SJCL) важно учитывать архитектурные особенности: библиотека исторически ориентирована на компактность, предсказуемость и минимализм зависимостей. Основной набор хеш-функций в SJCL включает SHA-256 и HMAC на его основе, тогда как SHA-3/Keccak не является частью базового ядра.

SHA-3 стандартизирован NIST и основан на алгоритме Keccak. Его ключевая особенность — внутренняя перестановка состояния (permutation) и разделение состояния на две части:

  • rate (r) — часть, куда поступают входные данные
  • capacity (c) — часть, обеспечивающая криптографическую стойкость

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

state = r + c

Хеширование происходит в два этапа:

  1. Absorbing phase (поглощение) Входные данные разбиваются на блоки и XOR’ятся с частью состояния rate, после чего применяется перестановка Keccak-f.

  2. Squeezing phase (выжимание) Из состояния последовательно извлекаются блоки выходного хеша, при необходимости с повторным применением перестановки.

Ключевое свойство: безопасность зависит от capacity, а не от длины выходного хеша напрямую.


Архитектура SJCL и место хеш-функций

В SJCL криптографические примитивы реализуются через унифицированные интерфейсы. Хеш-функции обычно реализуют API вида:

var hash = new sjcl.hash.sha256();
hash.update("data");
var result = hash.finalize();

или через статические методы:

sjcl.hash.sha256.hash("data");

Внутри библиотека ожидает, что хеш-объект поддерживает:

  • update(data)
  • finalize()
  • (иногда) reset()
  • внутреннее состояние для инкрементального хеширования

Проблема интеграции SHA-3 в SJCL

Поскольку SJCL не включает SHA-3 по умолчанию, возникает задача адаптации внешней реализации Keccak к интерфейсу sjcl.hash.

Сложность заключается в различии моделей:

  • SJCL SHA-256 — потоковая обработка с блоками 512 бит
  • Keccak — sponge construction с другим внутренним состоянием и режимом обработки

Поэтому интеграция требует либо:

  • обёртки над внешней библиотекой SHA-3
  • либо полной реализации Keccak в стиле SJCL

Подключение внешнего Keccak-движка

На практике чаще используется готовая реализация SHA-3, например из npm-пакетов (js-sha3, keccak, crypto-js с расширениями). Пример с использованием абстрактной функции Keccak:

function KeccakWrapper(bits) {
    this._keccak = new Keccak(bits);
}

KeccakWrapper.prototype.update = function (data) {
    this._keccak.update(data);
    return this;
};

KeccakWrapper.prototype.finalize = function () {
    return this._keccak.digest();
};

Далее создаётся адаптер под SJCL:

sjcl.hash.sha3_256 = function () {
    this._impl = new KeccakWrapper(256);
};

sjcl.hash.sha3_256.prototype.update = function (data) {
    this._impl.update(data);
};

sjcl.hash.sha3_256.prototype.finalize = function () {
    return this._impl.finalize();
};

Теперь API становится совместимым с остальными хешами SJCL:

var h = new sjcl.hash.sha3_256();
h.update("hello");
var digest = h.finalize();

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

SJCL активно использует собственный тип sjcl.bitArray, представляющий данные как массив 32-битных слов.

Keccak-реализации, напротив, часто работают с:

  • Uint8Array
  • строками
  • hex-представлением

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

function bitArrayToBytes(arr) {
    return sjcl.codec.bytes.fromBits(arr);
}

function bytesToBitArray(bytes) {
    return sjcl.codec.bytes.toBits(bytes);
}

Обёртка должна обеспечивать согласованность форматов:

KeccakWrapper.prototype.update = function (bitArray) {
    var bytes = bitArrayToBytes(bitArray);
    this._keccak.update(bytes);
};

Инкрементальное хеширование и ограничения SHA-3

В отличие от SHA-256, SHA-3 (Keccak) имеет другие характеристики производительности:

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

При этом он обеспечивает:

  • повышенную устойчивость к длине расширения (length extension attack не применим в той же форме)
  • гибкость выходной длины (SHAKE128 / SHAKE256)

SHAKE-функции и расширяемый вывод

В семействе Keccak присутствуют функции SHAKE:

  • SHAKE128
  • SHAKE256

Они позволяют получать произвольную длину выхода:

var shake = new KeccakWrapper(256);
shake.update("data");
var out = shake.finalize(64); // 64 байта

Для SJCL это требует расширения интерфейса finalize(length).


Использование SHA-3 в HMAC внутри SJCL

SJCL реализует HMAC через универсальный интерфейс хеш-функции:

var mac = new sjcl.misc.hmac(key, sjcl.hash.sha256);
mac.update("message");
var tag = mac.digest();

Для SHA-3 адаптер должен быть совместим:

var mac = new sjcl.misc.hmac(key, sjcl.hash.sha3_256);

Это возможно только если SHA-3-класс реализует идентичный API.


PBKDF2 и SHA-3

PBKDF2 в SJCL зависит от хеш-функции как параметра:

sjcl.misc.pbkdf2(password, salt, iterations, keyLength, sjcl.hash.sha3_256);

Критически важно, чтобы SHA-3:

  • корректно поддерживал reset состояния
  • не кэшировал промежуточные результаты
  • корректно обрабатывал повторные update()

Частые ошибки интеграции

1. Несовместимость форматов данных Keccak ожидает байты, SJCL работает с bitArray.

2. Повторное использование экземпляра hash Некорректный reset приводит к утечке состояния.

3. Несоответствие выходного формата SHA-3 возвращает Uint8Array, SJCL ожидает bitArray.

4. Игнорирование padding-логики Keccak использует собственное padding rule (multi-rate padding), несовместимое с SHA-2.


Минимальная реализация SHA3-интерфейса для SJCL

sjcl.hash.sha3_256 = function () {
    this._state = new Keccak(256);
};

sjcl.hash.sha3_256.prototype.update = function (bits) {
    this._state.update(sjcl.codec.bytes.fromBits(bits));
};

sjcl.hash.sha3_256.prototype.finalize = function () {
    var out = this._state.digest();
    return sjcl.codec.bytes.toBits(out);
};

sjcl.hash.sha3_256.prototype.reset = function () {
    this._state = new Keccak(256);
};

Производственные аспекты использования SHA-3 в браузере

При использовании SHA-3 в SJCL в реальных приложениях важно учитывать:

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

Для оптимизации применяют:

  • Web Workers
  • chunked processing
  • асинхронные очереди вычислений

Сравнение SHA-256 и SHA-3 в рамках SJCL

SHA-256 (SJCL native):

  • высокая скорость
  • компактная реализация
  • оптимизирована под JS

SHA-3 (external integration):

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

Архитектурная гибкость SJCL позволяет подключать SHA-3 как внешнюю реализацию при условии строгого соблюдения интерфейса sjcl.hash, корректного преобразования данных и аккуратной работы с состоянием хеш-функции.