Работа с бинарными данными

В библиотеке CryptoJS вся работа с криптографическими примитивами опирается на единый внутренний формат представления данных — WordArray. Это ключевая абстракция, через которую проходят ключи, сообщения, хэши и шифротексты. Понимание устройства этого формата критично для корректной работы с бинарными данными, особенно при взаимодействии с API браузера, Node.js и внешними сервисами.

Основной тип бинарных данных в CryptoJS — это объект CryptoJS.lib.WordArray. Он представляет собой массив 32-битных слов (words), дополненный информацией о фактической длине данных в байтах.

Структура выглядит следующим образом:

  • words: массив 32-битных целых чисел
  • sigBytes: количество значимых байт

Пример создания:

const wordArray = CryptoJS.lib.WordArray.create([0x12345678, 0x90abcdef], 8);

Здесь:

  • массив содержит два 32-битных слова
  • 8 означает, что используется 8 байт (то есть не все биты последнего слова значимы)

Особенности хранения

WordArray хранит данные в big-endian представлении внутри 32-битных слов. Это важно учитывать при ручной работе с байтами, так как порядок байтов внутри слова может отличаться от привычного побайтного представления.


Преобразование строк в бинарный формат

CryptoJS автоматически конвертирует строки в WordArray через кодировки из CryptoJS.enc.

UTF-8 кодирование

const data = CryptoJS.enc.Utf8.parse("hello");

Результат — WordArray, содержащий байтовое представление UTF-8 строки.

Hex кодирование

const data = CryptoJS.enc.Hex.parse("68656c6c6f");

Каждые два символа hex превращаются в байт.

Base64 кодирование

const data = CryptoJS.enc.Base64.parse("aGVsbG8=");

Base64 декодируется в исходные байты.


Обратное преобразование: WordArray → строка

Любой WordArray можно сериализовать обратно:

В hex

const hex = wordArray.toString(CryptoJS.enc.Hex);

В Base64

const base64 = wordArray.toString(CryptoJS.enc.Base64);

В UTF-8 строку

const text = wordArray.toString(CryptoJS.enc.Utf8);

Важно учитывать, что некорректная интерпретация бинарных данных как UTF-8 может привести к ошибкам декодирования.


Работа с байтами вручную

CryptoJS предоставляет низкоуровневый доступ к управлению бинарными данными через words и sigBytes.

Создание пустого WordArray

const wa = CryptoJS.lib.WordArray.create();

Добавление данных

Для конкатенации используется метод concat:

const a = CryptoJS.enc.Utf8.parse("hello ");
const b = CryptoJS.enc.Utf8.parse("world");

const result = a.concat(b);

После операции result содержит объединённые байты.


Обрезка и контроль длины данных

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

wordArray.sigBytes = 5;

После этого лишние байты игнорируются при сериализации.

Для корректировки внутреннего состояния используется метод clamp():

wordArray.clamp();

Он обнуляет лишние биты в последнем 32-битном слове, синхронизируя его с sigBytes.


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

В браузере бинарные данные часто представлены через ArrayBuffer или Uint8Array. CryptoJS не работает с ними напрямую, поэтому требуется конвертация.

Uint8Array → WordArray

function uint8ToWordArray(u8Array) {
    const words = [];
    for (let i = 0; i < u8Array.length; i++) {
        words[i >>> 2] |= u8Array[i] << (24 - (i % 4) * 8);
    }
    return CryptoJS.lib.WordArray.create(words, u8Array.length);
}

WordArray → Uint8Array

function wordArrayToUint8Array(wordArray) {
    const { words, sigBytes } = wordArray;
    const u8 = new Uint8Array(sigBytes);

    for (let i = 0; i < sigBytes; i++) {
        u8[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
    }

    return u8;
}

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

Все алгоритмы CryptoJS (SHA, MD5, RIPEMD160) принимают WordArray как вход.

const hash = CryptoJS.SHA256(CryptoJS.enc.Utf8.parse("message"));

Результат также является WordArray.


Бинарные данные в шифровании

При симметричном шифровании (AES, DES) данные должны быть представлены в бинарном виде.

const encrypted = CryptoJS.AES.encrypt(
    CryptoJS.enc.Utf8.parse("secret data"),
    "password"
);

Внутри происходит:

  • преобразование строки в WordArray
  • добавление padding (PKCS7 по умолчанию)
  • шифрование блоками по 128 бит

Padding и влияние на бинарное представление

CryptoJS автоматически применяет padding при шифровании. Это приводит к изменению длины бинарных данных.

Типичный padding:

  • PKCS7

Каждый добавленный байт содержит значение количества добавленных байтов.

Это критично при ручной обработке WordArray, так как:

  • sigBytes включает padding
  • words может содержать “мусорные” биты без clamp()

Клонирование бинарных данных

WordArray является изменяемым объектом. Простое присваивание создаёт ссылку, а не копию.

Глубокое копирование:

const copy = wordArray.clone();

Это важно при многократных операциях хэширования или шифрования, чтобы не испортить исходные данные.


Конкатенация бинарных блоков

WordArray можно объединять:

const a = CryptoJS.enc.Hex.parse("aa");
const b = CryptoJS.enc.Hex.parse("bb");

const combined = a.clone().concat(b);

Особенность: concat изменяет исходный объект, поэтому часто используют clone().


Частые ошибки при работе с бинарными данными

Потеря сигнатурной длины

Если не обновить sigBytes, результат сериализации может содержать лишние байты.

Отсутствие clamp()

После ручного изменения words обязательно:

wordArray.clamp();

Иначе возможны артефакты в старших битах.


Неверная кодировка

Типичная ошибка:

CryptoJS.enc.Utf8.parse(hexString)

Если данные в hex, нужно:

CryptoJS.enc.Hex.parse(hexString)

Производительность бинарных операций

WordArray не оптимизирован для высокопроизводительных потоковых операций. Основные ограничения:

  • отсутствие streaming API
  • копирование при concat
  • 32-битное выравнивание

Для больших массивов данных (сотни мегабайт) предпочтительнее использовать Web Crypto API или Node Buffer.


Взаимодействие с Node.js Buffer

В Node.js часто требуется конвертация между Buffer и WordArray.

Buffer → WordArray

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

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

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

WordArray → Buffer

function wordArrayToBuffer(wordArray) {
    const buffer = Buffer.alloc(wordArray.sigBytes);

    for (let i = 0; i < wordArray.sigBytes; i++) {
        buffer[i] = (wordArray.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
    }

    return buffer;
}

Бинарные данные в HMAC

HMAC в CryptoJS также полностью основан на WordArray:

const hmac = CryptoJS.HmacSHA256(
    CryptoJS.enc.Utf8.parse("data"),
    CryptoJS.enc.Utf8.parse("key")
);

Здесь и ключ, и сообщение — бинарные структуры.


Работа с потоковыми данными через имитацию блоков

Хотя CryptoJS не поддерживает потоковую обработку, можно имитировать её через последовательную конкатенацию блоков:

let acc = CryptoJS.lib.WordArray.create();

acc = acc.concat(CryptoJS.enc.Utf8.parse("part1"));
acc = acc.concat(CryptoJS.enc.Utf8.parse("part2"));

Однако каждый concat создаёт новый объект, что делает подход затратным.


Итоговая модель работы с бинарными данными

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

  1. Входные данные (строка / массив байт)
  2. Преобразование в WordArray
  3. Обработка алгоритмом (hash / cipher)
  4. Получение WordArray результата
  5. Сериализация через encoder (Hex/Base64/Utf8)

Понимание этой цепочки позволяет точно контролировать поведение криптографических операций и корректно интегрировать CryptoJS в системы, работающие с бинарными протоколами, файлами и сетевыми потоками.