Stanford JavaScript Crypto Library (SJCL) ориентирована на минималистичное, но криптографически корректное представление данных в JavaScript-окружении. При попытке интеграции с Java-криптографией, особенно с реализациями на базе Bouncy Castle, основная сложность возникает не в алгоритмах, а в различиях представления структурированных данных: ключей, векторов и режимов шифрования.
SJCL по умолчанию использует собственный формат сериализации:
Java и Bouncy Castle, напротив, опираются на:
byte[])Это приводит к необходимости унификации промежуточного слоя представления данных.
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));
Проблема возникает при передаче ключей между средами: требуется преобразование:
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-эквивалент не требуется, так как байтовый массив уже является нативным форматом.
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), иначе криптографический результат будет несовместим.
SJCL поддерживает AES в режимах CBC и CCM. На стороне Java чаще используется Bouncy Castle как расширение стандартного JCE.
var ciphertext = sjcl.encrypt("key", "message", {
mode: "cbc",
iv: sjcl.random.randomWords(4, 0)
});
JSON-результат содержит:
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));
SJCL использует собственную реализацию padding (bit-padding в некоторых режимах), тогда как Java:
Несовпадение padding — одна из главных причин несовместимости результатов шифрования.
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();
Ключевые параметры должны совпадать:
SJCL исторически использует SHA-256 в современных конфигурациях, но старые реализации могут отличаться.
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 поддерживает ECC (curve-based cryptography), но модель представления точек отличается от Java.
SJCL:
sjcl.ecc.elGamal.generateKeys(256);
Java (Bouncy Castle):
ECKeyPairGenerator gen = new ECKeyPairGenerator();
X9ECParameters params = CustomNamedCurves.getByName("secp256r1");
Ключевые отличия:
{
"x": "...",
"y": "...",
"curve": "c256"
}
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.
SJCL использует:
sjcl.codec.base64.fromBits(...)
Java:
Base64.getEncoder().encodeToString(bytes);
Несовместимость возникает при:
Java BigInteger.toByteArray() может добавлять sign bit,
что ломает ECC ключи при конвертации в SJCL.
Решение: ручная нормализация массива байтов.
SJCL генерирует IV в word-массиве:
sjcl.random.randomWords(4)
Java требует 16-byte array строго фиксированной длины.
SJCL:
Bouncy Castle:
Несовместимость возникает при несогласованной конфигурации PBKDF2/HMAC.
Типовая архитектура обмена:
SJCL выполняет клиентское шифрование
JSON пакет передаётся в Java backend
Bouncy Castle выполняет:
Для стабильной интеграции создаётся промежуточный адаптер:
Он обязан:
| Режим | SJCL | Bouncy Castle |
|---|---|---|
| CBC | поддерживается | поддерживается |
| CTR | поддерживается | поддерживается |
| CCM | поддерживается | ограниченно |
| GCM | частично | полностью |
Особенно критичен GCM: различие в обработке auth tag.
SJCL обеспечивает клиентскую криптографию на уровне браузера
Java + Bouncy Castle обеспечивает серверную верификацию и хранение
совместимость достигается только через строгую нормализацию: