Совместимость с Java и Bouncy Castle

Stanford JavaScript Crypto Library (SJCL) ориентирована на минималистичное, но криптографически корректное представление данных в JavaScript-окружении. При попытке интеграции с Java-криптографией, особенно с реализациями на базе Bouncy Castle, основная сложность возникает не в алгоритмах, а в различиях представления структурированных данных: ключей, векторов и режимов шифрования.

SJCL по умолчанию использует собственный формат сериализации:

  • ключи и случайные значения кодируются в виде массивов 32-битных слов
  • итоговые сообщения часто сериализуются в JSON-структуры
  • бинарные данные представляются через битовые операции и base64-подобные преобразования

Java и Bouncy Castle, напротив, опираются на:

  • ASN.1 структуры (в случае ключей и сертификатов)
  • стандартные байтовые массивы (byte[])
  • DER/PEM кодирование
  • строгую типизацию криптографических объектов

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


Базовая проблема: различие endian и word-based модели SJCL

SJCL оперирует 32-битными словами (big-endian логика внутри массива), тогда как Java работает на уровне байтов.

Типичный фрагмент SJCL:

var key = sjcl.hash.sha256.hash("password");

Результат:

[ 0x12345678, 0x9abcdef0, ... ]

В Java эквивалент:

MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest("password".getBytes(StandardCharsets.UTF_8));

Проблема возникает при передаче ключей между средами: требуется преобразование:

  • WordArray → byte[]
  • byte[] → WordArray

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

Преобразование SJCL WordArray в byte[]

function sjclWordsToBytes(words) {
    var bytes = [];
    for (var i = 0; i < words.length; i++) {
        var w = words[i];
        bytes.push((w >>> 24) & 0xff);
        bytes.push((w >>> 16) & 0xff);
        bytes.push((w >>> 8) & 0xff);
        bytes.push(w & 0xff);
    }
    return bytes;
}

Java-эквивалент не требуется, так как байтовый массив уже является нативным форматом.


Преобразование byte[] в SJCL формат

function bytesToSjclWords(bytes) {
    var words = [];
    for (var i = 0; i < bytes.length; i += 4) {
        words.push(
            (bytes[i] << 24) |
            (bytes[i + 1] << 16) |
            (bytes[i + 2] << 8) |
            (bytes[i + 3])
        );
    }
    return words;
}

Ключевой момент — строгое соблюдение порядка байтов (big-endian), иначе криптографический результат будет несовместим.


AES совместимость: SJCL и Bouncy Castle

SJCL поддерживает AES в режимах CBC и CCM. На стороне Java чаще используется Bouncy Castle как расширение стандартного JCE.

SJCL AES-CBC пример

var ciphertext = sjcl.encrypt("key", "message", {
    mode: "cbc",
    iv: sjcl.random.randomWords(4, 0)
});

JSON-результат содержит:

  • iv
  • salt
  • ct (ciphertext)
  • mode

Java Bouncy Castle AES-CBC эквивалент

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");

IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");

cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

byte[] encrypted = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8));

Критическое различие padding

SJCL использует собственную реализацию padding (bit-padding в некоторых режимах), тогда как Java:

  • PKCS5Padding
  • PKCS7Padding (в Bouncy Castle)

Несовпадение padding — одна из главных причин несовместимости результатов шифрования.


PBKDF2: согласование параметров

SJCL:

sjcl.misc.pbkdf2("password", "salt", 10000, 256);

Java (Bouncy Castle):

PBEKeySpec spec = new PBEKeySpec(
    "password".toCharArray(),
    saltBytes,
    10000,
    256
);

SecretKeyFactory factory =
    SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256", "BC");

byte[] key = factory.generateSecret(spec).getEncoded();

Ключевые параметры должны совпадать:

  • iteration count
  • salt (строго одинаковое байтовое представление)
  • hash function (SHA-1 vs SHA-256)
  • output length

SJCL исторически использует SHA-256 в современных конфигурациях, но старые реализации могут отличаться.


HMAC совместимость

SJCL:

sjcl.misc.hmac(key).encrypt(message);

Java:

Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "HmacSHA256");
mac.init(keySpec);

byte[] result = mac.doFinal(messageBytes);

Основная проблема — сериализация ключа:

  • SJCL: WordArray
  • Java: byte[]

ECC и Bouncy Castle

SJCL поддерживает ECC (curve-based cryptography), но модель представления точек отличается от Java.

SJCL:

sjcl.ecc.elGamal.generateKeys(256);

Java (Bouncy Castle):

ECKeyPairGenerator gen = new ECKeyPairGenerator();
X9ECParameters params = CustomNamedCurves.getByName("secp256r1");

Ключевые отличия:

  • SJCL абстрагирует кривые
  • Bouncy Castle требует явного указания параметров кривой
  • сериализация публичных ключей в SJCL — JSON-подобная структура
  • в Java — SubjectPublicKeyInfo (ASN.1)

Сериализация ключей между SJCL и Bouncy Castle

SJCL формат публичного ключа

{
  "x": "...",
  "y": "...",
  "curve": "c256"
}

Java формат (Bouncy Castle)

  • DER encoded SubjectPublicKeyInfo
  • X.509 структура

Преобразование ECC точки

Java → SJCL:

ECPoint q = publicKey.getQ();
byte[] x = q.getAffineXCoord().toBigInteger().toByteArray();
byte[] y = q.getAffineYCoord().toBigInteger().toByteArray();

SJCL требует:

{
  x: sjcl.bn.fromBits(...),
  y: sjcl.bn.fromBits(...)
}

Проблема — необходимость строгого совпадения curve parameters.


Base64 и бинарная совместимость

SJCL использует:

sjcl.codec.base64.fromBits(...)

Java:

Base64.getEncoder().encodeToString(bytes);

Несовместимость возникает при:

  • URL-safe base64 (Java)
  • стандартный base64 (SJCL)
  • отсутствие padding ‘=’

Типичные ошибки интеграции

1. Потеря ведущих нулей

Java BigInteger.toByteArray() может добавлять sign bit, что ломает ECC ключи при конвертации в SJCL.

Решение: ручная нормализация массива байтов.


2. Несовпадение IV

SJCL генерирует IV в word-массиве:

sjcl.random.randomWords(4)

Java требует 16-byte array строго фиксированной длины.


3. Разные алгоритмы SHA

SJCL:

  • SHA-256 (основной)
  • SHA-512 (опционально)

Bouncy Castle:

  • множество вариантов: SHA3, RIPEMD, Blake2

Несовместимость возникает при несогласованной конфигурации PBKDF2/HMAC.


Практическая схема интеграции SJCL ↔︎ Java

Типовая архитектура обмена:

  1. SJCL выполняет клиентское шифрование

  2. JSON пакет передаётся в Java backend

  3. Bouncy Castle выполняет:

    • расшифровку
    • валидацию HMAC
    • повторное шифрование при необходимости

Рекомендованный слой адаптации

Для стабильной интеграции создаётся промежуточный адаптер:

  • SJCL serializer (JS)
  • Java crypto adapter (Bouncy Castle wrapper)

Он обязан:

  • унифицировать encoding (UTF-8)
  • фиксировать endian (big-endian)
  • стандартизировать base64
  • фиксировать параметры алгоритмов

Совместимость режимов шифрования

Режим SJCL Bouncy Castle
CBC поддерживается поддерживается
CTR поддерживается поддерживается
CCM поддерживается ограниченно
GCM частично полностью

Особенно критичен GCM: различие в обработке auth tag.


Итоговая модель взаимодействия криптослоёв

  • SJCL обеспечивает клиентскую криптографию на уровне браузера

  • Java + Bouncy Castle обеспечивает серверную верификацию и хранение

  • совместимость достигается только через строгую нормализацию:

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