Хеширование с обновлением состояния (update/digest)

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

В Jsrsasign эта модель реализована через объект KJUR.crypto.MessageDigest, который поддерживает инкрементное обновление внутреннего состояния и финализацию результата через digest.


Любая криптографическая хеш-функция в инкрементной форме работает как конечный автомат с внутренним состоянием:

  • инициализация алгоритма (SHA-1, SHA-256, SHA-512 и др.)
  • последовательные обновления состояния входными блоками
  • финализация с выдачей итогового значения

Ключевая особенность — после каждого update состояние не теряется, а модифицируется.


Создание экземпляра MessageDigest

В Jsrsasign объект хеширования создаётся через фабричный метод:

var md = new KJUR.crypto.MessageDigest({alg: "sha256"});

Поддерживаемые алгоритмы включают:

  • sha1
  • sha256
  • sha512
  • md5 (не рекомендуется для криптографических целей)
  • и другие, доступные в реализации библиотеки

Внутри создаётся состояние алгоритма, аналогичное контексту в низкоуровневых реализациях OpenSSL-подобного типа.


Метод update: накопление данных

Основной метод инкрементного хеширования:

md.updateString("Hello ");
md.updateString("world");

Jsrsasign поддерживает несколько форматов входных данных:

Строки

md.updateString("data");

Данные интерпретируются как UTF-8 последовательность.

Hex-строки

md.updateHex("616263"); // "abc"

Каждая пара символов рассматривается как байт.

Байтовые массивы (через string/hex преобразования)

Прямой Uint8Array не всегда используется напрямую, поэтому часто применяется промежуточное преобразование.


Финализация через digest

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

var hash = md.digest();

Возвращаемое значение зависит от выбранного формата:

  • чаще всего hex-строка
  • иногда байтовое представление в строке

Пример полного цикла:

var md = new KJUR.crypto.MessageDigest({alg: "sha256"});

md.updateString("Hello ");
md.updateString("world");

var result = md.digest();

Поведение после digest

После вызова digest() объект:

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

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

md = new KJUR.crypto.MessageDigest({alg: "sha256"});

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

Основная область применения update/digest — работа с данными, поступающими порциями.

Пример сценария: обработка файла блоками

var md = new KJUR.crypto.MessageDigest({alg: "sha256"});

md.updateString(chunk1);
md.updateString(chunk2);
md.updateString(chunk3);

var hash = md.digest();

Если данные приходят из потока, модель остаётся той же:

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

Отличие от одноразового хеширования

Jsrsasign также предоставляет более простой способ:

KJUR.crypto.Util.hashString("sha256", "Hello world");

Но этот вариант:

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

Инкрементная модель через MessageDigest предпочтительна при любом масштабировании данных.


Внутренняя логика накопления блоков

Хеш-функции работают блоками фиксированного размера:

  • SHA-256: 512-битные блоки
  • SHA-512: 1024-битные блоки

Метод update не вызывает вычисление финального результата сразу. Вместо этого:

  1. данные накапливаются во внутреннем буфере
  2. при заполнении блока выполняется трансформация состояния
  3. остаток сохраняется до следующего вызова update

Это соответствует классической Merkle–Damgård конструкции.


Работа с разными типами входных данных

Jsrsasign допускает смешивание форматов:

md.updateString("abc");
md.updateHex("646566");
md.updateString("ghi");

Внутренне всё приводится к единому байтовому представлению.

Критический момент — согласованность кодировки:

  • updateString использует UTF-8
  • updateHex не зависит от кодировки

При смешивании важно учитывать, что строка и hex не эквивалентны на уровне представления байтов.


Повторное использование экземпляра

Некоторые сценарии требуют вычисления нескольких хешей:

var md = new KJUR.crypto.MessageDigest({alg: "sha256"});

md.updateString("A");
var h1 = md.digest();

md = new KJUR.crypto.MessageDigest({alg: "sha256"});
md.updateString("B");
var h2 = md.digest();

Попытка повторного update после digest без переинициализации может приводить к некорректным результатам.


Производительность инкрементного хеширования

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

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

Однако существует накладной расход:

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

Оптимальная стратегия — подавать данные крупными блоками.


Типичные ошибки при использовании update/digest

На практике встречаются характерные проблемы:

Повторный digest без нового экземпляра

md.updateString("a");
md.digest();
md.updateString("b"); // некорректно

Несогласованность кодировок

Смешивание UTF-8 строк и hex без контроля приводит к разным результатам хеша.

Слишком мелкие чанки

for (var i = 0; i < str.length; i++) {
  md.updateString(str[i]);
}

Такой подход создаёт значительные накладные расходы.


Сценарий потокового хеширования

Типичная архитектура обработки потоков:

function processStream(chunks) {
  var md = new KJUR.crypto.MessageDigest({alg: "sha256"});

  for (var i = 0; i < chunks.length; i++) {
    md.updateString(chunks[i]);
  }

  return md.digest();
}

Такая схема легко масштабируется до:

  • файловых потоков
  • WebSocket сообщений
  • частичной загрузки данных через API

Совместимость с криптографическими протоколами

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

  • HMAC-операциях (внутренние update для inner/outer pad)
  • цифровых подписях
  • проверке целостности сообщений

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