Реализация гибридного шифрования в SJCL

Гибридное шифрование сочетает в себе два подхода:

  • Симметричное шифрование — используется для быстрого шифрования данных (например, AES)
  • Асимметричное шифрование — применяется для безопасной передачи симметричного ключа (например, ECC)

Такая схема устраняет основные недостатки каждого подхода:

  • асимметричные алгоритмы медленные для больших объёмов данных
  • симметричные требуют защищённого канала для передачи ключа

В библиотеке SJCL отсутствует «готовый» API для гибридного шифрования, но все необходимые primitives присутствуют.


Основные компоненты SJCL для реализации

Ключевые модули библиотеки:

  • sjcl.cipher.aes — симметричное шифрование
  • sjcl.mode.gcm / sjcl.mode.ccm — режимы шифрования
  • sjcl.ecc — эллиптическая криптография
  • sjcl.hash.sha256 — хеширование
  • sjcl.misc.hkdf — генерация ключей
  • sjcl.random — криптографически стойкий генератор

Общая схема работы

  1. Генерация случайного симметричного ключа

  2. Шифрование данных симметричным алгоритмом

  3. Генерация временной ECC-пары ключей

  4. Вычисление общего секрета через ECDH

  5. Производный ключ используется для шифрования симметричного ключа

  6. Передача:

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

Генерация симметричного ключа

const aesKey = sjcl.random.randomWords(8, 0); // 256 бит

Используется массив слов (bitArray), являющийся базовым форматом данных в SJCL.


Шифрование данных (AES-GCM)

const iv = sjcl.random.randomWords(3, 0); // 96 бит
const aes = new sjcl.cipher.aes(aesKey);

const plaintext = sjcl.codec.utf8String.toBits("Секретные данные");

const ciphertext = sjcl.mode.gcm.encrypt(
  aes,
  plaintext,
  iv
);

Особенности:

  • GCM обеспечивает аутентификацию
  • IV должен быть уникальным для каждого шифрования

Генерация ECC-ключей

const curve = sjcl.ecc.curves.c256;

const senderKeys = sjcl.ecc.elGamal.generateKeys(curve, 0);
const recipientKeys = sjcl.ecc.elGamal.generateKeys(curve, 0);

В реальной системе:

  • получатель уже имеет постоянную пару ключей
  • отправитель генерирует временную пару (ephemeral)

Вычисление общего секрета (ECDH)

const sharedSecret = senderKeys.sec.dh(recipientKeys.pub);

На стороне получателя:

const sharedSecretReceiver = recipientKeys.sec.dh(senderKeys.pub);

Оба значения идентичны.


Производный ключ (HKDF)

Общий секрет не используется напрямую — он пропускается через функцию вывода ключей:

const derivedKey = sjcl.misc.hkdf(
  sharedSecret,
  256,
  null,
  "hybrid encryption"
);

Результат — криптографически устойчивый ключ длиной 256 бит.


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

const aesKeyBits = aesKey;

const wrapIv = sjcl.random.randomWords(3, 0);
const wrapCipher = new sjcl.cipher.aes(derivedKey);

const encryptedAesKey = sjcl.mode.gcm.encrypt(
  wrapCipher,
  aesKeyBits,
  wrapIv
);

Формирование итогового сообщения

Передаваемый объект должен содержать:

const payload = {
  ciphertext: sjcl.codec.base64.fromBits(ciphertext),
  iv: sjcl.codec.base64.fromBits(iv),
  encryptedKey: sjcl.codec.base64.fromBits(encryptedAesKey),
  wrapIv: sjcl.codec.base64.fromBits(wrapIv),
  senderPubKey: senderKeys.pub.get()
};

Публичный ключ отправителя сериализуется в JSON-совместимую структуру.


Дешифрование на стороне получателя

1. Восстановление публичного ключа отправителя

const senderPub = new sjcl.ecc.elGamal.publicKey(
  curve,
  payload.senderPubKey
);

2. Получение общего секрета

const sharedSecret = recipientKeys.sec.dh(senderPub);

3. Генерация производного ключа

const derivedKey = sjcl.misc.hkdf(
  sharedSecret,
  256,
  null,
  "hybrid encryption"
);

4. Расшифровка симметричного ключа

const wrapCipher = new sjcl.cipher.aes(derivedKey);

const decryptedAesKey = sjcl.mode.gcm.decrypt(
  wrapCipher,
  sjcl.codec.base64.toBits(payload.encryptedKey),
  sjcl.codec.base64.toBits(payload.wrapIv)
);

5. Расшифровка данных

const aes = new sjcl.cipher.aes(decryptedAesKey);

const plaintextBits = sjcl.mode.gcm.decrypt(
  aes,
  sjcl.codec.base64.toBits(payload.ciphertext),
  sjcl.codec.base64.toBits(payload.iv)
);

const plaintext = sjcl.codec.utf8String.fromBits(plaintextBits);

Управление случайностью

Перед использованием sjcl.random требуется накопление энтропии:

sjcl.random.startCollectors();

В браузере энтропия поступает из:

  • движений мыши
  • таймингов событий
  • системных источников (если доступны)

Проверка готовности:

sjcl.random.isReady();

Важные аспекты безопасности

1. Эфемерные ключи

  • обязательны для прямой секретности (Forward Secrecy)
  • не должны повторно использоваться

2. Уникальность IV

  • повторное использование IV в GCM приводит к компрометации

3. Проверка аутентичности

  • режим GCM автоматически проверяет целостность
  • при ошибке выбрасывается исключение

4. Контекст HKDF

  • строка "hybrid encryption" должна быть фиксированной и согласованной
  • защищает от атак повторного использования ключей

5. Размеры ключей

  • AES: минимум 128 бит, предпочтительно 256
  • ECC: минимум кривая P-256

Расширения и улучшения

Аутентификация отправителя

Добавление цифровой подписи:

const signature = senderSigningKey.sign(hash);

Проверка:

senderPubKey.verify(hash, signature);

Дополнительные данные (AAD)

GCM поддерживает аутентифицированные данные:

sjcl.mode.gcm.encrypt(aes, data, iv, aad);

Используется для защиты метаданных.


Сжатие перед шифрованием

Позволяет:

  • уменьшить размер данных
  • повысить стойкость к некоторым атакам анализа структуры

Ограничения SJCL

  • Отсутствие встроенной поддержки гибридных схем
  • Нет автоматической сериализации ключей
  • Ограниченная поддержка современных стандартов (например, X25519)
  • Зависимость от источников энтропии браузера

Практическое применение

Гибридное шифрование на базе SJCL применяется в:

  • защищённых веб-приложениях
  • клиентском шифровании перед отправкой данных
  • реализации end-to-end шифрования
  • браузерных криптографических протоколах

Реализация требует строгого контроля всех этапов: от генерации случайности до управления ключами и формата сообщений.