Работа в браузере с криптографическими библиотеками почти всегда
упирается в корректную обработку бинарных данных. В случае CryptoJS
ключевым внутренним форматом является WordArray, тогда как
веб-API оперирует ArrayBuffer, Blob и
File. Эти представления необходимо приводить друг к другу,
иначе любые операции хеширования, шифрования или подписи файлов будут
либо невозможны, либо некорректны по результату.
В браузере файлы, полученные через
<input type="file">, drag-and-drop или
fetch, представлены объектами File и
Blob. Основной способ доступа к их содержимому — чтение в
виде ArrayBuffer.
Классический поток работы выглядит так:
FileFileReaderArrayBufferconst fileInput = document.querySelector("input[type='file']");
fileInput.addEventListener("change", (event) => {
const file = event.target.files[0];
const reader = new FileReader();
reader.onl oad = function(e) {
const arrayBuffer = e.target.result;
console.log(arrayBuffer);
};
reader.readAsArrayBuffer(file);
});
На этом этапе данные уже находятся в бинарном виде, но CryptoJS не
работает напрямую с ArrayBuffer. Он использует собственную
структуру WordArray.
CryptoJS хранит данные в виде массива 32-битных слов. Это позволяет эффективно выполнять криптографические операции в JavaScript без работы с сырыми байтами на каждом шаге.
Основная проблема интеграции с Web API заключается в необходимости преобразования:
ArrayBuffer → WordArray
Это ключевой мост между браузерным миром и CryptoJS.
Типичная реализация конвертации выглядит следующим образом:
function arrayBufferToWordArray(arrayBuffer) {
const uint8Array = new Uint8Array(arrayBuffer);
const words = [];
for (let i = 0; i < uint8Array.length; i++) {
words[i >>> 2] |= uint8Array[i] << (24 - (i % 4) * 8);
}
return CryptoJS.lib.WordArray.create(words, uint8Array.length);
}
Здесь происходит побайтовая упаковка данных в 32-битные слова:
После преобразования данных можно применять любые алгоритмы: SHA-1, SHA-256, MD5 и другие.
Пример вычисления SHA-256 для файла:
function hashFile(file) {
const reader = new FileReader();
reader.onl oad = function(e) {
const arrayBuffer = e.target.result;
const wordArray = arrayBufferToWordArray(arrayBuffer);
const hash = CryptoJS.SHA256(wordArray).toString();
console.log(hash);
};
reader.readAsArrayBuffer(file);
}
В этом сценарии важно понимать, что весь файл загружается в память. Для небольших файлов это приемлемо, но для больших — становится проблемой.
FileReader.readAsArrayBuffer загружает файл целиком. Это
приводит к нескольким ограничениям:
CryptoJS сам по себе не поддерживает потоковое хеширование в классическом смысле (как Web Crypto API), поэтому приходится использовать обходные стратегии.
Для работы с большими файлами используется разбиение на блоки (chunking). Каждый блок последовательно добавляется в хеш.
Принцип:
Однако CryptoJS не предоставляет полноценного streaming API для всех алгоритмов, поэтому часто применяется накопительный подход.
function hashFileByChunks(file, chunkSize = 1024 * 1024) {
const reader = new FileReader();
let offset = 0;
const sha256 = CryptoJS.algo.SHA256.create();
reader.onl oad = function(e) {
const arrayBuffer = e.target.result;
const wordArray = arrayBufferToWordArray(arrayBuffer);
sha256.update(wordArray);
offset += chunkSize;
if (offset < file.size) {
readNextChunk();
} else {
const hash = sha256.finalize().toString();
console.log(hash);
}
};
function readNextChunk() {
const slice = file.slice(offset, offset + chunkSize);
reader.readAsArrayBuffer(slice);
}
readNextChunk();
}
Такой подход уменьшает нагрузку на память и позволяет обрабатывать большие файлы, хотя остаётся ограничение однопоточности JavaScript.
В некоторых сценариях файл не нужно напрямую загружать через input. Он может быть получен через сеть или сгенерирован динамически.
fetch("/file.bin")
.then(response => response.blob())
.then(blob => blob.arrayBuffer())
.then(arrayBuffer => {
const wordArray = arrayBufferToWordArray(arrayBuffer);
const hash = CryptoJS.MD5(wordArray).toString();
console.log(hash);
});
Blob предоставляет удобный промежуточный слой, особенно при работе с потоками данных.
Не все данные являются бинарными файлами. Иногда требуется обработка текстов, сериализованных в UTF-8.
В таких случаях используется TextEncoder:
const encoder = new TextEncoder();
const data = encoder.encode("пример строки");
const wordArray = CryptoJS.lib.WordArray.create(data);
const hash = CryptoJS.SHA1(wordArray).toString();
Этот путь обходится без File API, но сохраняет совместимость с CryptoJS.
Одной из наиболее распространённых проблем является неправильное преобразование байтов:
ArrayBuffer напрямую в CryptoJS// некорректно
CryptoJS.SHA256(arrayBuffer)
Такая операция не даст ожидаемого результата, поскольку библиотека интерпретирует вход как строку или WordArray, а не как бинарный буфер.
В контексте File API и ArrayBuffer важно учитывать альтернативу —
SubtleCrypto.
ArrayBufferCryptoJS:
Пример Web Crypto API:
const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
Этот вариант значительно проще, но не всегда доступен в старых окружениях или в некоторых ограниченных средах.
При интенсивной работе с файлами критично учитывать:
Особенно важно избегать лишних преобразований:
Blob → ArrayBuffer → Uint8Array → WordArray
Каждый дополнительный шаг увеличивает нагрузку на GC и снижает производительность.
Типичный кейс — проверка контрольной суммы файла перед загрузкой на сервер:
async function verifyFile(file, expectedHash) {
const buffer = await file.arrayBuffer();
const wordArray = arrayBufferToWordArray(buffer);
const hash = CryptoJS.SHA256(wordArray).toString();
return hash === expectedHash;
}
Здесь File API и CryptoJS взаимодействуют через единую точку преобразования, обеспечивая совместимость между браузерной моделью данных и криптографическим слоем библиотеки.