Шифрование JSON-объектов

JSON-объекты в JavaScript представляют собой структурированные данные, содержащие строки, числа, массивы и вложенные объекты. Криптографические алгоритмы работают только с последовательностями байтов или строками, поэтому перед шифрованием JSON всегда приводится к строковому виду.

Основной механизм преобразования:

const data = {
  user: "alice",
  role: "admin",
  active: true,
  permissions: ["read", "write", "delete"]
};

const jsonString = JSON.stringify(data);

JSON.stringify обеспечивает детерминированное преобразование структуры в строку. Важно учитывать, что:

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

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


Шифрование JSON с использованием AES

Crypto-js предоставляет реализацию симметричного шифрования, включая AES (Advanced Encryption Standard), который является базовым стандартом для защиты данных.

Шифрование JSON-строки выполняется через CryptoJS.AES.encrypt:

import CryptoJS from "crypto-js";

const data = {
  user: "alice",
  role: "admin"
};

const jsonString = JSON.stringify(data);

const secretKey = "super_secret_key";

const encrypted = CryptoJS.AES.encrypt(jsonString, secretKey).toString();

Результат encrypted представляет собой строку в формате Base64, содержащую зашифрованный блок данных.

Особенности процесса

При вызове AES.encrypt без дополнительных параметров библиотека автоматически:

  • генерирует случайный salt
  • формирует IV (initialization vector)
  • применяет PBKDF2 для получения ключа из строки пароля
  • использует режим CBC по умолчанию

Эти детали скрыты, но напрямую влияют на безопасность и воспроизводимость результата.


Дешифрование JSON-данных

Обратное преобразование требует того же ключа:

const decryptedBytes = CryptoJS.AES.decrypt(encrypted, secretKey);

const decryptedString = decryptedBytes.toString(CryptoJS.enc.Utf8);

const originalObject = JSON.parse(decryptedString);

Критически важные моменты:

  • без корректного ключа результат будет пустой строкой
  • Utf8 декодирование обязательно для восстановления исходного текста
  • JSON.parse выполняется только после успешной дешифровки

Ошибки на любом этапе приводят к невозможности восстановления структуры данных.


Работа с ключами и паролями

Использование строкового ключа напрямую допустимо, но не является безопасной практикой для продакшн-систем.

Crypto-js позволяет явно управлять параметрами ключа через PBKDF2:

const password = "user_password";

const salt = CryptoJS.lib.WordArray.random(128 / 8);

const key = CryptoJS.PBKDF2(password, salt, {
  keySize: 256 / 32,
  iterations: 10000
});

const iv = CryptoJS.lib.WordArray.random(128 / 8);

const encrypted = CryptoJS.AES.encrypt(jsonString, key, {
  iv: iv
}).toString();

Значение параметров

  • salt — предотвращает использование радужных таблиц
  • iterations — увеличивает стоимость перебора пароля
  • iv — обеспечивает уникальность шифротекста при одинаковом входе

При таком подходе необходимо сохранять salt и iv вместе с зашифрованными данными.


Формат хранения зашифрованного JSON

Часто требуется хранить не только результат шифрования, но и метаданные для расшифровки.

Типовая структура:

const payload = {
  ciphertext: encrypted,
  iv: iv.toString(),
  salt: salt.toString()
};

Такой объект затем снова сериализуется:

const storageString = JSON.stringify(payload);

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

const parsed = JSON.parse(storageString);

const decrypted = CryptoJS.AES.decrypt(
  parsed.ciphertext,
  key,
  { iv: CryptoJS.enc.Hex.parse(parsed.iv) }
);

const result = JSON.parse(decrypted.toString(CryptoJS.enc.Utf8));

Шифрование вложенных JSON-структур

JSON часто содержит сложные вложенные объекты:

const data = {
  user: {
    id: 1,
    profile: {
      name: "Alice",
      settings: {
        theme: "dark"
      }
    }
  }
};

Перед шифрованием структура не изменяется — сериализуется целиком:

const serialized = JSON.stringify(data);
const encrypted = CryptoJS.AES.encrypt(serialized, secretKey).toString();

После дешифрования структура восстанавливается без потери вложенности:

const decrypted = CryptoJS.AES.decrypt(encrypted, secretKey)
  .toString(CryptoJS.enc.Utf8);

const original = JSON.parse(decrypted);

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

Потеря структуры из-за некорректной сериализации

Использование ручной конкатенации строк вместо JSON.stringify приводит к неоднозначности данных.

Попытка шифрования объекта напрямую

CryptoJS.AES.encrypt({ user: "alice" }, secretKey);

Такой вызов приводит к преобразованию объекта в строку "[object Object]", что делает восстановление данных невозможным.

Несоответствие ключей

Даже различие в одном символе ключа делает дешифрование невозможным.

Отсутствие UTF-8 декодирования

decryptedBytes.toString(); // ошибка в большинстве случаев

Корректный вариант:

decryptedBytes.toString(CryptoJS.enc.Utf8);

Использование режима CBC и ручной конфигурации

Crypto-js позволяет задавать параметры шифрования явно:

const encrypted = CryptoJS.AES.encrypt(jsonString, key, {
  mode: CryptoJS.mode.CBC,
  padding: CryptoJS.pad.Pkcs7,
  iv: iv
});

Значение режимов

  • CBC — цепочечное шифрование блоков
  • Pkcs7 padding — выравнивание данных до размера блока

Корректная конфигурация особенно важна при взаимодействии с серверными реализациями AES.


Детерминизм и безопасность JSON-шифрования

Шифрование JSON-объектов должно учитывать два противоположных требования:

  • воспроизводимость результата при наличии ключа
  • недопустимость предсказуемости шифротекста

Использование IV и salt решает проблему предсказуемости, а строгая сериализация обеспечивает корректность структуры.

При отсутствии IV одинаковые JSON-данные приводят к одинаковому шифротексту, что создаёт криптографическую уязвимость.


Работа с массивами JSON

Массивы обрабатываются аналогично объектам:

const data = [
  { id: 1, value: "A" },
  { id: 2, value: "B" }
];

const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), secretKey).toString();

После восстановления:

const decrypted = JSON.parse(
  CryptoJS.AES.decrypt(encrypted, secretKey)
    .toString(CryptoJS.enc.Utf8)
);

Тип данных сохраняется полностью, включая порядок элементов массива.


Интеграция шифрования JSON в клиентские приложения

В браузерных приложениях Crypto-js часто применяется для:

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

Типовой сценарий:

const session = {
  token: "abc123",
  expires: Date.now() + 3600
};

const encryptedSession = CryptoJS.AES.encrypt(
  JSON.stringify(session),
  secretKey
).toString();

localStorage.setItem("session", encryptedSession);

При восстановлении:

const stored = localStorage.getItem("session");

const sessionData = JSON.parse(
  CryptoJS.AES.decrypt(stored, secretKey)
    .toString(CryptoJS.enc.Utf8)
);

Особенности производительности при работе с JSON

При увеличении объёма данных возрастает стоимость:

  • сериализации JSON
  • PBKDF2 (при использовании паролей)
  • AES-операций на больших строках

На практике узким местом становится именно JSON.stringify и JSON.parse при больших вложенных структурах, а не сам AES.

Оптимизация достигается через:

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