Контрольная сумма — это короткое фиксированное значение, вычисляемое из содержимого файла с помощью криптографической хеш-функции. Она используется для проверки целостности данных: если файл изменился хотя бы на один байт, его хеш изменится полностью.
В Web Crypto API вычисление контрольных сумм реализовано через метод
subtle.digest, который поддерживает алгоритмы семейства
SHA.
Web Crypto API предоставляет доступ к следующим алгоритмам:
Для проверки целостности файлов чаще всего используется SHA-256.
Метод crypto.subtle.digest принимает:
ArrayBuffer или
TypedArrayasync function hashData(data) {
const encoder = new TextEncoder();
const buffer = encoder.encode(data);
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
return bufferToHex(hashBuffer);
}
function bufferToHex(buffer) {
const bytes = new Uint8Array(buffer);
return [...bytes]
.map(b => b.toString(16).padStart(2, "0"))
.join("");
}
Чаще всего задача возникает при загрузке файла пользователем:
<input type="file" id="fileInput">
Чтение файла:
const input = document.getElementById("fileInput");
input.addEventListener("change", async (event) => {
const file = event.target.files[0];
if (!file) return;
const arrayBuffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
const hashHex = bufferToHex(hashBuffer);
console.log("SHA-256:", hashHex);
});
Метод digest работает только с целым массивом
данных, что означает:
Это создаёт ограничения:
Хотя Web Crypto API не поддерживает инкрементальное хеширование напрямую, можно реализовать обработку файла по частям:
Blob.slice)digestОднако это не даёт реального выигрыша по памяти, потому что итоговый буфер всё равно собирается полностью.
Альтернативный подход — использовать сторонние реализации (например, pure JS или WASM), поддерживающие потоковое хеширование.
Результат digest — это ArrayBuffer. Его
нужно преобразовать:
function bufferToHex(buffer) {
return [...new Uint8Array(buffer)]
.map(b => b.toString(16).padStart(2, "0"))
.join("");
}
function bufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = "";
bytes.forEach(b => binary += String.fromCharCode(b));
return btoa(binary);
}
Сравнение контрольной суммы:
async function verifyFile(file, expectedHash) {
const buffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
const hash = bufferToHex(hashBuffer);
return hash === expectedHash;
}
const dropZone = document.getElementById("dropZone");
dropZone.addEventListener("drop", async (event) => {
event.preventDefault();
const file = event.dataTransfer.files[0];
const buffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
console.log(bufferToHex(hashBuffer));
});
1. Асинхронность Все операции выполняются через
Promise, что важно учитывать при проектировании UI.
2. Отсутствие потоковой обработки Нельзя обрабатывать данные по мере поступления.
3. Ограничения памяти Браузер может ограничить размер доступного буфера.
4. Поддержка браузерами Web Crypto API поддерживается большинством современных браузеров, но требует HTTPS.
Факторы, влияющие на скорость:
SHA-256 обычно является хорошим балансом между скоростью и безопасностью.
Пример с отображением прогресса:
async function hashFileWithProgress(file, onProgress) {
const chunkSize = 1024 * 1024 * 2; // 2MB
let offset = 0;
let chunks = [];
while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize);
chunks.push(await chunk.arrayBuffer());
offset += chunkSize;
onProgress(offset / file.size);
}
const fullBuffer = concatenateBuffers(chunks);
const hashBuffer = await crypto.subtle.digest("SHA-256", fullBuffer);
return bufferToHex(hashBuffer);
}
function concatenateBuffers(buffers) {
let totalLength = buffers.reduce((sum, b) => sum + b.byteLength, 0);
let result = new Uint8Array(totalLength);
let offset = 0;
buffers.forEach(buffer => {
result.set(new Uint8Array(buffer), offset);
offset += buffer.byteLength;
});
return result.buffer;
}
Когда требуется обработка очень больших файлов:
Вычисление хеша может блокировать основной поток. Перенос в Worker:
// main.js
const worker = new Worker("worker.js");
worker.postMessage(file);
worker.onmess age = (e) => {
console.log("Hash:", e.data);
};
// worker.js
self.onmess age = async (e) => {
const file = e.data;
const buffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
const hash = [...new Uint8Array(hashBuffer)]
.map(b => b.toString(16).padStart(2, "0"))
.join("");
self.postMessage(hash);
};
Контрольные суммы дополняют:
Они не обеспечивают аутентификацию, но гарантируют неизменность данных.
async function hashMultiple(files) {
const results = [];
for (const file of files) {
const buffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
results.push({
name: file.name,
hash: bufferToHex(hashBuffer)
});
}
return results;
}
const fileMap = new Map();
fileMap.set(hash, file.name);
Возможные проблемы:
NotSupportedError — неподдерживаемый алгоритмDataError — некорректные данныеПроверка:
if (!crypto.subtle) {
throw new Error("Web Crypto API не поддерживается");
}
Типичная схема:
subtle.digestТакой подход полностью выполняется на стороне клиента без передачи данных на сервер, что повышает приватность и снижает нагрузку на инфраструктуру.