Согласование форматов ключей и параметров

В основе всей работы Stanford JavaScript Crypto Library лежит единый тип представления бинарных данных — bitArray. Это не строка и не классический ArrayBuffer, а специализированная структура, оптимизированная под криптографические операции.

bitArray в SJCL — это массив 32-битных слов, где первые 4 бита служат для хранения длины в битах. Именно это отличие чаще всего становится источником несовместимости при попытке интеграции с другими библиотеками.

Ключевые свойства bitArray:

  • хранит длину данных в первых элементах
  • использует 32-битные операции для ускорения криптографии
  • не совместим напрямую с Uint8Array
  • требует явного кодирования при выходе за пределы SJCL

Типичный пример создания:

const arr = sjcl.codec.utf8String.toBits("secret");

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


Кодеки и преобразование типов

SJCL строго разделяет внутренние и внешние представления данных. Для этого используется модуль sjcl.codec.

Основные кодеки:

  • utf8String — строки ↔︎ bitArray
  • hex — шестнадцатеричное представление
  • base64 — стандартное представление для передачи данных
  • base32 — редко используемый, но поддерживаемый формат

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

const bits = sjcl.codec.utf8String.toBits("data");
const hex = sjcl.codec.hex.fromBits(bits);
const base64 = sjcl.codec.base64.fromBits(bits);

Здесь важно понимать: все кодеки работают только через bitArray. Любая попытка передать строку напрямую в криптографическую функцию приведёт к некорректным результатам или ошибке.


Ключи как bitArray и как строки: критическое расхождение

В SJCL ключ никогда не является “строкой” в классическом смысле. Даже если API принимает пароль, внутри он всегда преобразуется в bitArray.

Существует два принципиально разных сценария:

1. Пароль как строка

sjcl.encrypt("password", "message");

В этом случае происходит:

  • UTF-8 кодирование строки
  • PBKDF2-деривация ключа
  • получение AES-ключа в виде bitArray

2. Ключ как bitArray

const key = sjcl.codec.hex.toBits("00112233445566778899aabbccddeeff");

Такой ключ уже считается готовым криптографическим материалом.


PBKDF2 и нормализация параметров

При использовании паролей SJCL применяет PBKDF2 (Password-Based Key Derivation Function 2), и здесь возникает ключевая проблема согласования параметров между системами.

Параметры PBKDF2 в SJCL:

  • salt — случайная соль (bitArray)
  • iter — количество итераций
  • ks — размер ключа в словах (32-битных)
  • prf — HMAC функция

Типичный объект параметров:

{
  iter: 10000,
  ks: 128 / 32,
  salt: sjcl.random.randomWords(4)
}

Критическая деталь: ks измеряется не в байтах, а в 32-битных словах. Это часто вызывает несовместимость с OpenSSL и WebCrypto.


Формат зашифрованного объекта

Результат sjcl.encrypt возвращается в JSON-структуре, которая содержит строго определённые поля:

{
  "iv": "...",
  "v": 1,
  "iter": 10000,
  "ks": 128,
  "salt": "...",
  "ct": "...",
  "mode": "ccm",
  "adata": ""
}

Здесь происходит смешение кодировок:

  • iv, salt, ct — base64
  • числовые параметры — обычные числа
  • adata — строка

Несовместимость форматов IV и salt

Одна из самых частых ошибок при интеграции — неправильная интерпретация iv и salt.

В SJCL:

  • iv — bitArray → base64
  • salt — bitArray → base64

Но в других системах:

  • IV часто 16 байт (AES-CBC)
  • salt может быть строкой или hex

Пример проблемной ситуации:

  • WebCrypto возвращает ArrayBuffer
  • SJCL ожидает bitArray
  • base64 кодирование не учитывает endian-особенности SJCL

Согласование с WebCrypto и внешними библиотеками

При интеграции с Web Crypto API возникает необходимость явного преобразования:

bitArray → Uint8Array

function bitArrayToUint8Array(arr) {
  const bytes = sjcl.codec.hex.fromBits(arr);
  const len = bytes.length / 2;
  const out = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    out[i] = parseInt(bytes.substr(i * 2, 2), 16);
  }
  return out;
}

Uint8Array → bitArray

function uint8ArrayToBitArray(u8) {
  let hex = "";
  for (let i = 0; i < u8.length; i++) {
    hex += u8[i].toString(16).padStart(2, "0");
  }
  return sjcl.codec.hex.toBits(hex);
}

Эти преобразования необходимы, потому что SJCL не использует нативные бинарные буферы.


HMAC и согласование ключей

В HMAC SJCL также использует bitArray, но проблема возникает при передаче ключей между системами.

const key = sjcl.codec.utf8String.toBits("key");
const hmac = new sjcl.misc.hmac(key);

Если тот же ключ в другой библиотеке представлен как строка UTF-8 или hex, результат HMAC будет полностью отличаться.


AES режимы и их параметрическая несовместимость

SJCL поддерживает несколько режимов AES:

  • CCM (по умолчанию)
  • CBC
  • GCM (в ограниченной реализации)

Параметры режима:

  • iv — обязательно 128 бит
  • adata — дополнительные данные (AAD)
  • tag — только в CCM/GCM

Проблема согласования:

  • в WebCrypto tag отделён от ciphertext
  • в SJCL tag встроен в результат ct

Нормализация JSON перед передачей между системами

При передаче зашифрованных данных между сервисами требуется строгая нормализация:

  1. все бинарные поля → base64
  2. числовые параметры без преобразования
  3. строки в UTF-8
  4. исключение внутренних SJCL структур

Пример нормализованного объекта:

{
  iv: base64(ivBits),
  salt: base64(saltBits),
  ct: base64(cipherBits),
  iter: 10000,
  ks: 128,
  mode: "ccm"
}

Типовые ошибки согласования форматов

На практике встречаются повторяющиеся ошибки:

  • использование hex вместо base64 для iv
  • передача Uint8Array напрямую в SJCL
  • несоответствие длины ключа (bits vs bytes)
  • игнорирование 4-битного заголовка bitArray
  • смешение UTF-8 и Latin-1 при кодировании строк

Стратегия обеспечения совместимости

Корректная работа с SJCL в гетерогенных системах требует строгой дисциплины:

  • единый слой кодирования/декодирования
  • запрет на прямую работу с string в криптографических функциях
  • централизованное управление salt/iv генерацией
  • явная фиксация всех параметров PBKDF2
  • контроль формата JSON на границе системы

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