В задачах криптографического хэширования ключевое ограничение классических API — необходимость иметь всё входное сообщение целиком до начала вычисления хэша. Для небольших строк это не проблема, но при работе с файлами, сетевыми потоками, логами или бинарными данными такой подход становится неэффективным и иногда невозможным из-за ограничений памяти.
В SJCL (Stanford JavaScript Crypto Library) хэш-функции построены таким образом, чтобы поддерживать инкрементальную обработку данных, позволяя формировать хэш постепенно, порциями, без необходимости хранить весь объём данных в памяти.
В SJCL хэш-функции реализуют стандартный интерфейс:
reset() — сброс состоянияupdate(data) — добавление данных в потокfinalize() — получение итогового хэшаПример для SHA-256:
const hash = new sjcl.hash.sha256();
hash.update("hello ");
hash.update("world");
const result = hash.finalize();
Ключевой момент: метод update можно вызывать сколько
угодно раз, постепенно подавая данные.
SJCL не работает напрямую со строками или Uint8Array как основным типом. Внутренний формат данных — это bitArray, массив 32-битных слов с дополнительной информацией о длине.
Пример преобразования строки:
const bits = sjcl.codec.utf8String.toBits("data");
Обратное преобразование:
const str = sjcl.codec.utf8String.fromBits(bits);
При потоковом хэшировании важно понимать: каждый вызов
update преобразует входные данные в bitArray и добавляет их
к внутреннему состоянию алгоритма.
При обработке больших файлов данные обычно поступают порциями (chunks). SJCL позволяет обрабатывать их без накопления всего содержимого.
Пример имитации потоковой обработки:
const sha = new sjcl.hash.sha256();
function processChunk(chunk) {
const bits = sjcl.codec.utf8String.toBits(chunk);
sha.update(bits);
}
// имитация потоков
processChunk("first part of ");
processChunk("large dataset ");
processChunk("streaming input");
const digest = sha.finalize();
Для бинарных данных (например, ArrayBuffer) используется
преобразование в bitArray через sjcl.codec.hex или ручное
преобразование.
Пример обработки ArrayBuffer:
function arrayBufferToBits(buffer) {
const bytes = new Uint8Array(buffer);
let hex = "";
for (let i = 0; i < bytes.length; i++) {
hex += bytes[i].toString(16).padStart(2, "0");
}
return sjcl.codec.hex.toBits(hex);
}
const sha = new sjcl.hash.sha256();
sha.update(arrayBufferToBits(chunk1));
sha.update(arrayBufferToBits(chunk2));
const digest = sha.finalize();
Этот подход используется при интеграции SJCL с Web APIs, такими как FileReader или Streams API.
При работе с файлами в браузере данные обычно читаются частями через
FileReader или ReadableStream.
Пример с FileReader:
const file = input.files[0];
const reader = new FileReader();
const sha = new sjcl.hash.sha256();
let offset = 0;
const chunkSize = 64 * 1024;
reader.onl oad = function(e) {
const buffer = e.target.result;
sha.update(arrayBufferToBits(buffer));
offset += chunkSize;
readNext();
};
function readNext() {
const slice = file.slice(offset, offset + chunkSize);
reader.readAsArrayBuffer(slice);
}
readNext();
Здесь хэш вычисляется инкрементально, без загрузки всего файла в память.
Каждая хэш-функция SJCL хранит:
Это позволяет корректно продолжать вычисления между вызовами
update.
Важно учитывать:
finalize() объект нельзя повторно использовать
без reset()update влияет на результатОднократный вариант:
const digest = sjcl.hash.sha256.hash("message");
Потоковый вариант:
const sha = new sjcl.hash.sha256();
sha.update(sjcl.codec.utf8String.toBits("me"));
sha.update(sjcl.codec.utf8String.toBits("ssage"));
const digest = sha.finalize();
Оба варианта дают одинаковый результат при идентичной последовательности данных.
Потоковое хэширование в SJCL выгодно в следующих сценариях:
Однако следует учитывать:
update могут снижать
производительность1. Потеря состояния между chunk-ами
function process(chunk) {
const sha = new sjcl.hash.sha256(); // ошибка
sha.update(...);
}
Правильный подход — сохранять экземпляр:
const sha = new sjcl.hash.sha256();
2. Повторный finalize
const h = sha.finalize();
const h2 = sha.finalize(); // некорректно
После финализации состояние фиксируется.
3. Несогласованное кодирование данных
Если часть данных передаётся как UTF-8, а часть как hex без унификации, результат будет некорректным.
В современных приложениях SJCL часто используется вместе с потоками:
const sha = new sjcl.hash.sha256();
const stream = file.stream().getReader();
function read() {
stream.read().then(({ done, value }) => {
if (done) {
const digest = sha.finalize();
return;
}
sha.update(arrayBufferToBits(value));
read();
});
}
read();
Такая схема позволяет обрабатывать данные по мере поступления, не блокируя UI.
HMAC в SJCL также поддерживает инкрементальное обновление:
const key = sjcl.codec.utf8String.toBits("secret");
const hmac = new sjcl.misc.hmac(key, sjcl.hash.sha256);
hmac.update(sjcl.codec.utf8String.toBits("part1"));
hmac.update(sjcl.codec.utf8String.toBits("part2"));
const mac = hmac.digest();
Принцип идентичен хэшированию: данные подаются частями, результат фиксируется в конце.
При работе с произвольными источниками данных (например, генераторами, бинарными протоколами, телеметрией) важно соблюдать:
SJCL не навязывает транспортный слой, поэтому ответственность за корректную потоковую сборку лежит на уровне приложения.
bitArray выступает промежуточным универсальным форматом:
Это делает возможной единообразную обработку текста, бинарных данных и структурированных сообщений в одной цепочке вычислений.