В контексте криптографии на стороне клиента в JavaScript библиотека SJCL предоставляет набор низкоуровневых примитивов, на базе которых строятся протоколы обмена ключами и симметричное шифрование. Одним из ключевых этапов является преобразование общего секрета, полученного, например, через ECDH, в устойчивый симметричный ключ, пригодный для AES.
Результатом выполнения протокола Диффи—Хеллмана на эллиптических
кривых в SJCL является значение типа sjcl.bitArray. Это не
строка и не бинарный массив в привычном смысле, а специализированная
структура, оптимизированная для криптографических операций.
Пример получения общего секрета:
const alice = sjcl.ecc.elGamal.generateKeys(256);
const bob = sjcl.ecc.elGamal.generateKeys(256);
const secretAlice = alice.sec.dh(bob.pub);
const secretBob = bob.sec.dh(alice.pub);
secretAlice и secretBob совпадают и
представляют общий секрет, но использовать его напрямую как ключ AES
недопустимо.
Сырой результат ECDH имеет ряд проблем:
Поэтому требуется этап деривации ключа (KDF — Key Derivation Function).
В SJCL реализована функция PBKDF2 через
sjcl.misc.pbkdf2, которая может использоваться для
преобразования общего секрета в ключевой материал.
Базовая форма:
sjcl.misc.pbkdf2(password, salt, iterations, keyLength, prf)
Где:
password — исходный секрет
(sjcl.bitArray)salt — дополнительная энтропияiterations — число итерацийkeyLength — длина ключа в битахprf — хэш-функция (обычно HMAC-SHA256)Типичный подход:
const sharedSecret = alice.sec.dh(bob.pub);
const salt = sjcl.random.randomWords(4, 0);
const aesKey = sjcl.misc.pbkdf2(
sharedSecret,
salt,
10000,
256,
sjcl.misc.hmac
);
Полученный aesKey уже может использоваться в
AES-256.
SJCL использует объект sjcl.cipher.aes, который
принимает ключ в формате bitArray.
const aes = new sjcl.cipher.aes(aesKey);
const ciphertext = sjcl.mode.ccm.encrypt(
aes,
sjcl.codec.utf8String.toBits("secret message"),
iv,
[],
128
);
Соль выполняет несколько критических функций:
В протоколах ECDH соль часто формируется как:
Хотя в SJCL отсутствует полноценная реализация HKDF, аналогичная схема может быть построена вручную с использованием SHA-256.
Общая структура HKDF:
const prk = sjcl.misc.hmac(
sjcl.hash.sha256,
salt
).encrypt(sharedSecret);
const info = sjcl.codec.utf8String.toBits("session-key");
const okm = sjcl.misc.hmac(
sjcl.hash.sha256,
prk
).encrypt(info);
Итоговый okm используется как ключ AES.
Более простой вариант без PBKDF2:
const hash = sjcl.hash.sha256.hash(sharedSecret.concat(salt));
const aesKey = hash.slice(0, 8); // 256 бит = 8 слов SJCL
Этот подход менее устойчив, но применяется в легковесных протоколах.
AES требует строго фиксированных размеров:
SJCL оперирует 32-битными словами, поэтому преобразование выглядит как:
const key256 = sjcl.bitArray.clamp(aesKey, 256);
Приводит к слабой диффузии и потенциальной утечке структуры данных.
Делает систему уязвимой к повторным атакам при одинаковых сессиях.
Снижает стоимость перебора ключа при атаке.
Ключ должен быть привязан к конкретной сессии, иначе возможны replay-атаки.
// генерация ключевых пар
const A = sjcl.ecc.elGamal.generateKeys(256);
const B = sjcl.ecc.elGamal.generateKeys(256);
// общий секрет
const shared = A.sec.dh(B.pub);
// соль сессии
const salt = sjcl.random.randomWords(4, 0);
// деривация ключа
const key = sjcl.misc.pbkdf2(
shared,
salt,
15000,
256,
sjcl.misc.hmac
);
// AES контекст
const aes = new sjcl.cipher.aes(key);
// шифрование
const iv = sjcl.random.randomWords(4, 0);
const encrypted = sjcl.mode.ccm.encrypt(
aes,
sjcl.codec.utf8String.toBits("payload"),
iv,
[],
128
);
Корректная деривация симметричного ключа определяет стойкость всей системы поверх ECDH. Даже при идеальной реализации обмена ключами, отсутствие корректного KDF превращает общий секрет в слабое место.
В SJCL это особенно критично из-за ориентации библиотеки на клиентский JavaScript, где окружение потенциально недоверенное и подвержено анализу.
SJCL активно использует bitArray как универсальный
формат, что позволяет:
Однако при интеграции с внешними системами часто требуется:
const hex = sjcl.codec.hex.fromBits(key);
const base64 = sjcl.codec.base64.fromBits(key);
Общая схема деривации симметричного ключа в SJCL сводится к цепочке:
bitArray)Эта цепочка обеспечивает криптографическую изоляцию между протоколом обмена ключами и симметричным шифрованием, исключая прямую зависимость AES-ключа от структуры эллиптической кривой.