Конвертация между форматами

Внутреннее представление данных в CryptoJS строится вокруг структуры WordArray. Это не строка и не массив байтов в привычном JavaScript-смысле, а специализированный контейнер, оптимизированный для криптографических операций.

WordArray хранит данные в виде массива 32-битных слов:

  • каждое слово содержит 4 байта
  • дополнительно хранится поле sigBytes, указывающее фактическую длину значимых байтов

Такой подход позволяет эффективно выполнять побитовые операции, хеширование и шифрование без лишних преобразований.

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

Здесь:

  • массив чисел — это 32-битные блоки
  • 8 — количество значимых байт

Кодировки как механизм преобразования строк

CryptoJS не работает напрямую со строками в криптографических алгоритмах. Любая строка сначала преобразуется в WordArray через кодировку.

Основные кодировки находятся в CryptoJS.enc:

  • Utf8
  • Latin1
  • Hex
  • Base64

Каждая кодировка реализует два ключевых метода:

  • parse() — строка → WordArray
  • stringify() — WordArray → строка

UTF-8 как основная кодировка

UTF-8 является стандартной кодировкой для большинства криптографических операций, так как корректно обрабатывает Unicode.

Преобразование строки в WordArray

const message = "Hello CryptoJS";
const wordArray = CryptoJS.enc.Utf8.parse(message);

Обратное преобразование

const original = CryptoJS.enc.Utf8.stringify(wordArray);

Внутри происходит побайтовое кодирование символов Unicode в UTF-8 последовательность.


Hex-формат и его особенности

Hex (шестнадцатеричное представление) используется для визуализации бинарных данных и совместимости с низкоуровневыми системами.

Строка → Hex → WordArray

const hexString = "48656c6c6f";
const wordArray = CryptoJS.enc.Hex.parse(hexString);

WordArray → Hex

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

Особенность Hex:

  • каждые 2 символа строки = 1 байт
  • часто используется для вывода hash-значений (SHA-256, MD5)

Base64 как транспортный формат

Base64 применяется для безопасной передачи бинарных данных в текстовых протоколах (JSON, HTTP, email).

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

const wordArray = CryptoJS.enc.Utf8.parse("CryptoJS");
const base64 = CryptoJS.enc.Base64.stringify(wordArray);

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

const parsed = CryptoJS.enc.Base64.parse(base64);
const text = CryptoJS.enc.Utf8.stringify(parsed);

Base64 увеличивает размер данных примерно на 33%, но обеспечивает совместимость с текстовыми форматами.


Конвертация между форматами через WordArray

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

UTF-8 → Hex

const wa = CryptoJS.enc.Utf8.parse("data");
const hex = CryptoJS.enc.Hex.stringify(wa);

Hex → Base64

const wa = CryptoJS.enc.Hex.parse(hex);
const base64 = CryptoJS.enc.Base64.stringify(wa);

Любое преобразование строится по цепочке:

Строка → WordArray → другой формат


Работа с ArrayBuffer и Uint8Array

В современных JavaScript-приложениях часто требуется взаимодействие с Web APIs и бинарными буферами.

CryptoJS не работает напрямую с ArrayBuffer, но преобразование возможно вручную.

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 wordArrayToUint8(wa) {
    const u8 = new Uint8Array(wa.sigBytes);
    for (let i = 0; i < wa.sigBytes; i++) {
        const byte = (wa.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
        u8[i] = byte;
    }
    return u8;
}

Такой мост необходим при работе с:

  • fetch API
  • WebCrypto API
  • файловыми потоками

JSON-сериализация WordArray

WordArray нельзя напрямую безопасно сериализовать в JSON, так как структура содержит внутренние поля и 32-битные слова.

Проблемный вариант

JSON.stringify(wordArray);

Результат:

  • потеря данных
  • некорректная структура

Корректный подход через Base64

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

const json = JSON.stringify({
    data: base64
});

Восстановление:

const parsed = JSON.parse(json);
const wa = CryptoJS.enc.Base64.parse(parsed.data);

Base64 является стандартным решением для сериализации криптографических данных.


Потери при неверной кодировке

Одной из частых ошибок является неправильный выбор кодировки при преобразовании.

Пример ошибки

const wa = CryptoJS.enc.Latin1.parse("Привет");

Latin1 не поддерживает кириллицу, что приводит к искажению данных.

Правильный вариант:

const wa = CryptoJS.enc.Utf8.parse("Привет");

Отличия Latin1 и UTF-8 в CryptoJS

Latin1:

  • каждый символ = 1 байт
  • ограничен диапазоном 0–255
  • подходит только для ASCII и расширенного латинского набора

UTF-8:

  • переменная длина (1–4 байта)
  • поддерживает Unicode полностью
  • стандарт для современных систем

Конвертация произвольных строковых представлений

Иногда требуется преобразовать данные между нестандартными форматами вручную.

Пример цепочки преобразований

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

const hex = CryptoJS.enc.Hex.stringify(utf8Wa);
const base64 = CryptoJS.enc.Base64.stringify(utf8Wa);
const latin1 = CryptoJS.enc.Latin1.stringify(utf8Wa);

Каждое представление интерпретирует один и тот же WordArray по-разному.


Внутренний механизм stringify и parse

Каждая кодировка реализует одинаковый интерфейс:

  • parse(string) → WordArray
  • stringify(WordArray) → string

Пример концептуальной реализации Hex

parse: function(hex) {
    const words = [];
    for (let i = 0; i < hex.length; i += 2) {
        words.push(parseInt(hex.substr(i, 2), 16));
    }
    return WordArray.create(words);
}

Потоковая конвертация в цепочках шифрования

При использовании алгоритмов шифрования преобразования происходят автоматически:

const encrypted = CryptoJS.AES.encrypt("text", "key");
const base64 = encrypted.toString();

Здесь результат по умолчанию возвращается в Base64, так как это безопасный текстовый формат.

Дешифрование:

const bytes = CryptoJS.AES.decrypt(base64, "key");
const original = bytes.toString(CryptoJS.enc.Utf8);

Частые ошибки при конвертации форматов

Потеря сигнальных байт

Игнорирование sigBytes приводит к захвату мусорных данных из последнего 32-битного слова.

Неправильное использование toString()

wordArray.toString()

Без указания кодировки по умолчанию используется Hex, что часто приводит к неожиданному результату.


Унификация форматов в приложениях

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

  • вход: UTF-8 строки
  • промежуточный формат: WordArray
  • хранение/передача: Base64
  • отладка: Hex

Такая схема минимизирует ошибки кодировок и упрощает взаимодействие между системами.