Форматы сериализации зашифрованных данных

Сериализация зашифрованных данных в CryptoJS опирается на внутреннюю модель представления криптографических результатов и механизм преобразования этих структур в строки и обратно. В библиотеке нет единственного «формата шифрования» — вместо этого используется комбинация объектов CipherParams, сериализаторов (format) и представлений бинарных данных (WordArray), которые вместе определяют, как именно будет выглядеть результат шифрования и как он будет восстановлен при расшифровке.

Вся криптография в CryptoJS строится вокруг структуры WordArray. Это внутреннее представление бинарных данных в виде массива 32-битных слов.

Ключевые особенности:

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

Типичная структура:

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

Именно WordArray используется для:

  • ключей шифрования
  • IV (initialization vector)
  • результата шифрования (ciphertext)
  • хэшей

Преобразования:

  • Hex:

    CryptoJS.enc.Hex.stringify(wordArray)
  • Base64:

    CryptoJS.enc.Base64.stringify(wordArray)
  • UTF-8:

    CryptoJS.enc.Utf8.stringify(wordArray)

Обратное преобразование выполняется через parse.


CipherParams как контейнер результата шифрования

Результат любого шифрования в CryptoJS инкапсулируется в объект CipherParams.

Он содержит:

  • ciphertext — зашифрованные данные (WordArray)
  • key — ключ (опционально, обычно не сериализуется)
  • iv — вектор инициализации
  • salt — соль (если используется режим OpenSSL)
  • algorithm — алгоритм
  • mode и padding — режим работы и дополнение

Пример структуры:

{
  ciphertext: WordArray,
  salt: WordArray,
  iv: WordArray,
  algorithm: AES,
  mode: CBC,
  padding: PKCS7
}

Этот объект не предназначен для прямой передачи — он должен быть сериализован.


Формат OpenSSL и строка Salted__

Наиболее распространённый формат сериализации в CryptoJS — OpenSSL-совместимый.

При использовании:

CryptoJS.AES.encrypt("text", "password")

результат автоматически преобразуется в строку:

U2FsdGVkX1...

Структура OpenSSL формата

Строка включает:

  • фиксированный заголовок: Salted__ (8 байт)
  • 8 байт соли
  • зашифрованный payload

Фактическая структура в бинарном виде:

Salted__ + salt + ciphertext

Далее всё это кодируется в Base64.


EVP_BytesToKey и роль соли

При использовании строкового пароля CryptoJS применяет алгоритм derivation ключа EVP_BytesToKey.

Вход:

  • password
  • salt

Выход:

  • key
  • iv

Особенности:

  • используется MD5 (в классической реализации CryptoJS)
  • несколько итераций хэширования
  • соль влияет на конечный ключ и IV

Это объясняет, почему одинаковый пароль даёт разные ciphertext при разных salt.


Формат сериализации format

CryptoJS использует объект format, который определяет, как CipherParams превращается в строку и обратно.

Базовый формат:

CryptoJS.format.OpenSSL

Он реализует два метода:

  • stringify(cipherParams)
  • parse(str)

stringify

Преобразует:

  • CipherParams → WordArray → Base64 строка

Логика:

  1. добавляет salt (если есть)
  2. объединяет с ciphertext
  3. кодирует в Base64

parse

Обратный процесс:

  1. декодирование Base64
  2. извлечение Salted__
  3. выделение salt
  4. выделение ciphertext

Hex и Base64 как альтернативные форматы

Hex формат

Используется для удобства отладки:

ciphertext.toString(CryptoJS.enc.Hex)

Особенности:

  • увеличивает размер данных примерно в 2 раза
  • не содержит метаданных (salt, iv)

Подходит только для простых сценариев.


Base64 формат

Более компактный, чем hex:

ciphertext.toString(CryptoJS.enc.Base64)

Особенности:

  • стандарт де-факто для передачи бинарных данных
  • не содержит структуры OpenSSL без дополнительной упаковки

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

Для сложных сценариев используется ручная сериализация в JSON.

Пример:

const encrypted = CryptoJS.AES.encrypt("text", "password");

const json = {
  ct: encrypted.ciphertext.toString(CryptoJS.enc.Base64),
  iv: encrypted.iv && encrypted.iv.toString(),
  s: encrypted.salt && encrypted.salt.toString()
};

Особенности:

  • полная контролируемость структуры
  • несовместимость с OpenSSL-автоматикой
  • необходимость ручного восстановления

Восстановление данных из сериализованного формата

При десериализации требуется восстановить CipherParams:

const cipherParams = CryptoJS.lib.CipherParams.create({
  ciphertext: CryptoJS.enc.Base64.parse(json.ct),
  iv: CryptoJS.enc.Hex.parse(json.iv),
  salt: CryptoJS.enc.Hex.parse(json.s)
});

После этого возможно:

CryptoJS.AES.decrypt(cipherParams, password)

Отличия форматов сериализации

OpenSSL формат

  • автоматический
  • содержит salt
  • совместим с OpenSSL CLI
  • не требует ручной сборки структуры

Base64 ciphertext

  • минимальный
  • не содержит метаданных
  • требует внешнего хранения iv/salt

Hex формат

  • человекочитаемый
  • используется в debugging
  • неэффективен по размеру

JSON формат

  • полностью кастомный
  • требует ручной логики восстановления
  • подходит для API и хранилищ

Влияние режима шифрования на сериализацию

Режимы шифрования влияют на необходимость хранения дополнительных параметров:

  • CBC → нужен IV
  • GCM → нужен IV + authTag (в CryptoJS ограниченно поддерживается)
  • ECB → не требует IV, но считается небезопасным

Поэтому сериализация часто расширяется:

{
  ct,
  iv,
  salt,
  mode,
  tag
}

Внутренняя совместимость format и CipherParams

CryptoJS допускает замену форматов:

CryptoJS.AES.encrypt("text", "password", {
  format: MyCustomFormat
});

Это позволяет:

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

Особенности работы с потоковой сериализацией

CryptoJS не поддерживает потоковое шифрование на уровне format, но допускает поэтапное накопление WordArray:

  • частичное обновление данных
  • объединение блоков
  • финализация через .finalize()

Сериализация применяется только после завершения процесса.


Практическая структура полного зашифрованного объекта

В реальных приложениях итоговый формат часто включает:

{
  v: 1,
  alg: "AES",
  mode: "CBC",
  iter: 1000,
  salt: "...",
  iv: "...",
  ct: "..."
}

Такой подход позволяет:

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