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

Хеширование — это процесс преобразования произвольного объёма входных данных в фиксированную строку байтов (хеш). В контексте Web Crypto API особое значение имеет работа именно с бинарными данными, поскольку большинство криптографических операций происходит на уровне байтов, а не строк.

Браузерный API ориентирован на использование типов ArrayBuffer, TypedArray и DataView. Это обеспечивает предсказуемость и безопасность при обработке данных, исключая неоднозначности, связанные с кодировками строк.


Метод crypto.subtle.digest

Основным инструментом для хеширования является метод:

crypto.subtle.digest(algorithm, data)

Параметры:

  • algorithm — строка с именем алгоритма ("SHA-1", "SHA-256", "SHA-384", "SHA-512")
  • data — бинарные данные (ArrayBuffer или TypedArray)

Возвращаемое значение:

  • Promise, который разрешается в ArrayBuffer с результатом хеширования

Поддерживаемые алгоритмы

Web Crypto API предоставляет доступ к следующим алгоритмам хеширования:

  • SHA-1 — устаревший, не рекомендуется для криптографической защиты
  • SHA-256 — стандартный и широко используемый
  • SHA-384 — усиленный вариант SHA-256
  • SHA-512 — обеспечивает максимальную криптостойкость среди доступных

Преобразование данных в бинарный формат

Строки необходимо явно преобразовывать в бинарный формат. Для этого используется TextEncoder:

const encoder = new TextEncoder();
const data = encoder.encode("Пример строки");

Результат — Uint8Array, который можно напрямую передать в digest.


Пример хеширования строки

async function hashString(str) {
    const encoder = new TextEncoder();
    const data = encoder.encode(str);

    const hashBuffer = await crypto.subtle.digest("SHA-256", data);

    return new Uint8Array(hashBuffer);
}

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

Работа с бинарными данными часто возникает при обработке файлов, сетевых пакетов или буферов.

async function hashBuffer(buffer) {
    const hash = await crypto.subtle.digest("SHA-512", buffer);
    return new Uint8Array(hash);
}

Если данные представлены в виде Uint8Array, их можно передать напрямую:

const data = new Uint8Array([1, 2, 3, 4]);
const hash = await crypto.subtle.digest("SHA-256", data);

Преобразование результата в читаемый формат

Хеш возвращается в виде бинарного массива. Для отображения его обычно переводят в шестнадцатеричную строку:

function toHex(buffer) {
    return Array.from(new Uint8Array(buffer))
        .map(b => b.toString(16).padStart(2, "0"))
        .join("");
}

Использование:

const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hexHash = toHex(hashBuffer);

Работа с файлами

Хеширование файлов требует чтения их содержимого как ArrayBuffer:

async function hashFile(file) {
    const buffer = await file.arrayBuffer();
    const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
    return toHex(hashBuffer);
}

Ограничения и особенности

1. Асинхронность Метод digest всегда возвращает Promise, даже для небольших данных.

2. Отсутствие потокового хеширования Web Crypto API не поддерживает инкрементальное хеширование (как, например, в Node.js). Все данные должны быть доступны целиком.

3. Неизменяемость входных данных Переданный буфер не модифицируется в процессе хеширования.

4. Производительность Нативная реализация в браузере значительно быстрее JavaScript-библиотек.


Работа с большими объёмами данных

Из-за отсутствия потокового API возникает проблема при работе с большими файлами. Возможные подходы:

  • загрузка файла целиком (ограничено памятью)
  • ручное разбиение на части с использованием внешних библиотек
  • использование Web Workers для разгрузки основного потока

Использование ArrayBuffer и TypedArray

Web Crypto API принимает различные формы бинарных данных:

// ArrayBuffer
const buffer = new ArrayBuffer(8);

// TypedArray
const uint8 = new Uint8Array(buffer);

// DataView
const view = new DataView(buffer);

Все эти структуры можно передавать в digest.


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

Сравнение хешей должно выполняться осторожно, особенно в криптографических сценариях. Простое сравнение строк может быть уязвимо к тайминговым атакам.

Безопасный вариант — побайтовое сравнение:

function compareHashes(a, b) {
    if (a.length !== b.length) return false;

    let result = 0;
    for (let i = 0; i < a.length; i++) {
        result |= a[i] ^ b[i];
    }
    return result === 0;
}

Типичные сценарии использования

  • проверка целостности файлов
  • хранение паролей (в сочетании с солью и KDF)
  • генерация уникальных идентификаторов
  • подпись данных (в комбинации с асимметричной криптографией)

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

Хеширование часто используется вместе с:

  • crypto.subtle.sign — для цифровых подписей
  • crypto.subtle.verify — для проверки подписи
  • crypto.subtle.deriveKey — для генерации ключей

Ошибки и обработка исключений

Ошибки возникают при:

  • передаче неподдерживаемого алгоритма
  • некорректном формате данных
  • использовании API в небезопасном контексте (HTTP)

Пример обработки:

try {
    const hash = await crypto.subtle.digest("SHA-256", data);
} catch (e) {
    console.error("Ошибка хеширования:", e);
}

Безопасный контекст

Web Crypto API доступен только в защищённой среде:

  • HTTPS
  • localhost

Попытка использования в небезопасном контексте приведёт к ошибке.


Кодировки и их влияние

Ошибки часто возникают из-за неправильной обработки строк:

  • "abc" в UTF-8 ≠ "abc" в UTF-16
  • всегда использовать TextEncoder для предсказуемого результата

Практические замечания

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

Минимальный полный пример

async function hashExample() {
    const encoder = new TextEncoder();
    const data = encoder.encode("Hello, WebCrypto");

    const hashBuffer = await crypto.subtle.digest("SHA-256", data);

    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");

    console.log(hashHex);
}

hashExample();

Архитектурные преимущества Web Crypto API

  • использование нативных криптографических библиотек браузера
  • защита от ошибок реализации
  • высокая производительность
  • стандартизированный интерфейс

Частые ошибки

  • передача строки вместо бинарных данных
  • забытый await
  • неправильная интерпретация результата (ArrayBuffer вместо строки)
  • попытка использовать API вне HTTPS

Хеширование бинарных данных в Web Crypto API — это строго типизированный, асинхронный процесс, требующий точного контроля форматов данных и понимания особенностей работы с буферами.