Хеширование файлов и бинарных данных

Библиотека Crypto-js оперирует не обычными строками JavaScript, а специальной структурой — WordArray. Это абстракция для хранения бинарных данных, представленная как массив 32-битных слов.

Файлы, буферы и любые бинарные данные необходимо привести к этому формату перед хешированием.

Основные способы получения WordArray:

const CryptoJS = require("crypto-js");

// Из строки (UTF-8 по умолчанию)
const wordArray = CryptoJS.enc.Utf8.parse("example");

// Из hex-строки
const wordArrayHex = CryptoJS.enc.Hex.parse("48656c6c6f");

// Из Base64
const wordArrayBase64 = CryptoJS.enc.Base64.parse("SGVsbG8=");

При работе с файлами в браузере или Node.js чаще всего используются ArrayBuffer, Uint8Array или Buffer, которые необходимо конвертировать.


Преобразование ArrayBuffer и Buffer

В браузере (File API)

function arrayBufferToWordArray(buffer) {
  const bytes = new Uint8Array(buffer);
  const words = [];

  for (let i = 0; i < bytes.length; i++) {
    words[(i / 4) | 0] |= bytes[i] << (24 - 8 * (i % 4));
  }

  return CryptoJS.lib.WordArray.create(words, bytes.length);
}

В Node.js

function bufferToWordArray(buffer) {
  const words = [];

  for (let i = 0; i < buffer.length; i++) {
    words[(i / 4) | 0] |= buffer[i] << (24 - 8 * (i % 4));
  }

  return CryptoJS.lib.WordArray.create(words, buffer.length);
}

Хеширование файлов целиком

После преобразования бинарных данных в WordArray можно использовать стандартные алгоритмы.

Пример с SHA-256:

const hash = CryptoJS.SHA256(wordArray);
console.log(hash.toString(CryptoJS.enc.Hex));

Другие поддерживаемые алгоритмы:

  • CryptoJS.MD5
  • CryptoJS.SHA1
  • CryptoJS.SHA224
  • CryptoJS.SHA256
  • CryptoJS.SHA384
  • CryptoJS.SHA512
  • CryptoJS.SHA3
  • CryptoJS.RIPEMD160

Потоковое хеширование (для больших файлов)

Хеширование крупных файлов целиком может привести к проблемам с памятью. В таких случаях применяется поэтапная обработка (streaming).

Crypto-js поддерживает инкрементальное обновление хеша через объект-алгоритм.

Пример:

const sha256 = CryptoJS.algo.SHA256.create();

sha256.update(CryptoJS.enc.Utf8.parse("chunk1"));
sha256.update(CryptoJS.enc.Utf8.parse("chunk2"));

const hash = sha256.finalize();

Чтение файла частями в браузере

function hashFile(file) {
  return new Promise((resolve, reject) => {
    const chunkSize = 1024 * 1024; // 1MB
    const reader = new FileReader();
    const sha256 = CryptoJS.algo.SHA256.create();

    let offset = 0;

    reader.onl oad = function (e) {
      const wordArray = arrayBufferToWordArray(e.target.result);
      sha256.update(wordArray);

      offset += chunkSize;

      if (offset < file.size) {
        readNext();
      } else {
        const hash = sha256.finalize();
        resolve(hash.toString(CryptoJS.enc.Hex));
      }
    };

    reader.oner ror = reject;

    function readNext() {
      const slice = file.slice(offset, offset + chunkSize);
      reader.readAsArrayBuffer(slice);
    }

    readNext();
  });
}

Чтение файла частями в Node.js

const fs = require("fs");

function hashFile(path) {
  return new Promise((resolve, reject) => {
    const stream = fs.createReadStream(path);
    const sha256 = CryptoJS.algo.SHA256.create();

    stream.on("data", (chunk) => {
      const wordArray = bufferToWordArray(chunk);
      sha256.update(wordArray);
    });

    stream.on("end", () => {
      const hash = sha256.finalize();
      resolve(hash.toString(CryptoJS.enc.Hex));
    });

    stream.on("error", reject);
  });
}

Кодировки результата

Результат хеширования — это WordArray, который можно преобразовать в разные форматы:

const hash = CryptoJS.SHA256("data");

hash.toString(CryptoJS.enc.Hex);     // hex
hash.toString(CryptoJS.enc.Base64);  // base64
hash.toString(CryptoJS.enc.Latin1);  // latin1

Hex используется чаще всего для хранения и сравнения.


Сравнение хешей

Сравнение должно выполняться по строковому представлению:

const hash1 = CryptoJS.SHA256("file1").toString();
const hash2 = CryptoJS.SHA256("file2").toString();

if (hash1 === hash2) {
  console.log("Файлы идентичны");
}

Особенности работы с бинарными данными

1. Порядок байтов (endianness) Crypto-js использует big-endian представление. При ручной сборке WordArray важно учитывать смещение:

bytes[i] << (24 - 8 * (i % 4))

2. Размер данных Второй параметр WordArray.create(words, length) обязателен — он указывает точное количество байтов.

3. Неизменяемость результата Метод finalize() завершает вычисление. После этого объект нельзя использовать повторно.


Хеширование изображений и произвольных файлов

Пример в браузере:

document.querySelector("input").addEventListener("change", async (e) => {
  const file = e.target.files[0];
  const hash = await hashFile(file);
  console.log(hash);
});

Поддерживаются любые типы файлов:

  • изображения (PNG, JPEG)
  • видео
  • архивы
  • бинарные исполняемые файлы

Проверка целостности

Хеширование применяется для:

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

Пример:

const expectedHash = "abc123...";
const actualHash = await hashFile(file);

if (expectedHash === actualHash) {
  console.log("Файл не поврежден");
}

Оптимизация производительности

Размер чанка

  • Маленький → больше операций, выше нагрузка CPU
  • Большой → выше потребление памяти

Оптимальный диапазон: 512KB – 4MB

Асинхронность

  • В браузере: FileReader не блокирует UI
  • В Node.js: stream работает неблокирующе

Повторное использование объектов

Алгоритмы (CryptoJS.algo.*) создаются один раз и используются последовательно.


Ограничения Crypto-js

  • Нет нативной поддержки потоков — требуется ручная реализация
  • Нет прямой работы с ArrayBuffer
  • Производительность ниже, чем у Web Crypto API

Сравнение с Web Crypto API

Характеристика Crypto-js Web Crypto API
Поддержка потоков Нет Ограничена
Производительность Ниже Выше (нативная)
Поддержка Node.js Да Частично
Простота Высокая Средняя

Комбинированное использование

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

  • Web Crypto API — для браузера
  • Crypto-js — для совместимости и fallback

Практические сценарии

1. Генерация хеша загружаемого файла

  • Проверка перед отправкой на сервер

2. Кэширование

  • Хеш используется как ключ

3. Сравнение версий файлов

  • Быстрое определение изменений

4. Дедупликация

  • Исключение одинаковых файлов

Типичные ошибки

Ошибка: хеширование строки вместо бинарных данных

CryptoJS.SHA256(file); // неправильно

Решение:

CryptoJS.SHA256(arrayBufferToWordArray(buffer));

Ошибка: игнорирование длины WordArray

CryptoJS.lib.WordArray.create(words); // некорректно

Решение:

CryptoJS.lib.WordArray.create(words, length);

Ошибка: повторное использование finalize

sha256.finalize();
sha256.update(...); // ошибка

Расширение: HMAC для файлов

Для аутентификации используется HMAC:

const hmac = CryptoJS.HmacSHA256(wordArray, "secret-key");

Потоковый вариант:

const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, "secret-key");

hmac.update(chunk1);
hmac.update(chunk2);

const result = hmac.finalize();

Вывод формата с префиксом

Иногда требуется формат:

const hash = CryptoJS.SHA256(wordArray).toString();
const formatted = `sha256:${hash}`;

Работа с очень большими файлами (>1GB)

Подход:

  • потоковое чтение
  • обработка чанков
  • минимизация промежуточных структур

Crypto-js справляется, но при экстремальных размерах предпочтительнее нативные решения.


Контроль памяти

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

Проверка корректности реализации

Контроль через известные тестовые значения:

CryptoJS.SHA256("abc").toString();
// должно быть:
// ba7816bf8f01cfea414140de5dae2223...

Безопасность

  • MD5 и SHA1 не подходят для криптографической защиты
  • рекомендуется использовать SHA-256 и выше
  • для аутентификации — HMAC

Взаимодействие с сервером

Часто используется схема:

  1. Клиент вычисляет хеш файла
  2. Отправляет хеш на сервер
  3. Сервер проверяет наличие файла
  4. При необходимости запрашивает загрузку

Итоговая архитектура обработки

  1. Чтение файла частями
  2. Конвертация в WordArray
  3. Инкрементальное обновление хеша
  4. Финализация
  5. Преобразование в строку

Такая схема масштабируется и применяется в реальных системах обработки больших данных.