Встроенный механизм хеширования в SubtleCrypto
предоставляет метод digest, который принимает на вход весь
массив данных целиком. Это означает:
Такая модель подходит для небольших данных, но становится проблемой при обработке файлов большого размера или потоков.
Классические криптографические библиотеки (например, OpenSSL) поддерживают следующий паттерн:
В Web Crypto API отсутствует аналогичная модель. Метод
crypto.subtle.digest работает как чистая функция:
const hash = await crypto.subtle.digest("SHA-256", data);
Это означает, что:
update() несколько разСамый очевидный обход — накопление всех чанков в один буфер.
async function hashChunks(chunks) {
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const combined = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
combined.se t(chunk, offset);
offset += chunk.length;
}
return crypto.subtle.digest("SHA-256", combined);
}
Недостатки:
При работе с потоками (например, fetch) можно собирать
данные постепенно:
async function hashStream(stream) {
const reader = stream.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
return hashChunks(chunks);
}
Фактически это тот же буферизационный подход, но адаптированный под поток.
Для настоящего инкрементального хеширования применяются JavaScript-библиотеки, реализующие алгоритмы вручную:
hash.jscrypto-jsspark-md5Пример с crypto-js:
const sha256 = CryptoJS.algo.SHA256.create();
sha256.update(CryptoJS.lib.WordArray.create(chunk1));
sha256.update(CryptoJS.lib.WordArray.create(chunk2));
const hash = sha256.finalize();
Преимущества:
update()Недостатки:
Более эффективный вариант — использование WASM-реализаций криптографии:
hash-wasmПример:
import { createSHA256 } from 'hash-wasm';
const hasher = await createSHA256();
hasher.init();
hasher.update(chunk1);
hasher.update(chunk2);
const hash = hasher.digest();
Преимущества:
Иногда применяется стратегия «дерева хешей» (Merkle tree):
async function hashChunk(chunk) {
return new Uint8Array(await crypto.subtle.digest("SHA-256", chunk));
}
async function hashTree(chunks) {
const hashes = await Promise.all(chunks.map(hashChunk));
return hashChunks(hashes);
}
Особенности:
Для работы с файлами можно читать их по частям:
async function hashFile(file, chunkSize = 1024 * 1024) {
const chunks = [];
let offset = 0;
while (offset < file.size) {
const slice = file.slice(offset, offset + chunkSize);
const buffer = await slice.arrayBuffer();
chunks.push(new Uint8Array(buffer));
offset += chunkSize;
}
return hashChunks(chunks);
}
Это позволяет:
Однако финальное хеширование всё равно требует объединения.
Можно построить поток обработки:
function createHashingStream() {
const chunks = [];
return new TransformStream({
transform(chunk, controller) {
chunks.push(chunk);
controller.enqueue(chunk);
},
async flush() {
const hash = await hashChunks(chunks);
console.log(new Uint8Array(hash));
}
});
}
Такой подход полезен для:
Но не решает проблему отсутствия incremental digest.
| Подход | Потоковость | Производительность | Память | Нативность |
|---|---|---|---|---|
| Буферизация | Нет | Высокая | Плохо | Да |
| Stream + buffer | Частично | Средняя | Плохо | Да |
| JS-библиотеки | Да | Ниже | Хорошо | Нет |
| WebAssembly | Да | Высокая | Хорошо | Частично |
| Merkle tree | Да | Высокая | Хорошо | Да |
Небольшие данные (до нескольких MB):
crypto.subtle.digest без обходных решенийБольшие файлы:
Потоки (network streaming):
crypto-jsПараллельная обработка:
Критичная безопасность и производительность:
Причины отсутствия incremental API:
Это приводит к тому, что Web Crypto остаётся:
Эффективная работа с хешированием в браузере строится на комбинировании:
SubtleCrypto — для финальных операцийТакой подход позволяет компенсировать ограничения Web Crypto API и реализовать масштабируемую обработку данных без потери производительности и контроля.