Использование SubtleCrypto совместно с SJCL

SubtleCrypto (Web Crypto API) предоставляет нативный криптографический слой браузера, реализованный на уровне платформы и значительно более быстрый и безопасный по сравнению с чисто JavaScript-реализациями. SJCL (Stanford JavaScript Crypto Library), в свою очередь, представляет собой полностью JS-библиотеку, работающую поверх стандартных типов JavaScript и использующую собственное представление бинарных данных.

Совместное использование этих двух систем требует понимания ключевого различия: SubtleCrypto работает с ArrayBuffer/TypedArray, тогда как SJCL использует собственный тип sjcl.bitArray.


SubtleCrypto

Основной тип данных:

  • ArrayBuffer
  • Uint8Array
  • CryptoKey

Пример:

const enc = new TextEncoder();
const data = enc.encode("hello"); // Uint8Array

SJCL

Основной тип данных:

  • sjcl.bitArray

Пример:

const bits = sjcl.codec.utf8String.toBits("hello");

Проблема совместимости

Любая интеграция требует преобразования:

  • ArrayBuffer → sjcl.bitArray
  • sjcl.bitArray → ArrayBuffer

Без этого невозможно смешивать:

  • AES-GCM (SubtleCrypto)
  • AES/CCM/OCB (SJCL)
  • HMAC, PBKDF2 и др.

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

ArrayBuffer → SJCL

function arrayBufferToBitArray(buffer) {
    const uint8 = new Uint8Array(buffer);
    const words = [];

    for (let i = 0; i < uint8.length; i += 4) {
        words.push(
            (uint8[i] << 24) |
            (uint8[i + 1] << 16) |
            (uint8[i + 2] << 8) |
            (uint8[i + 3] || 0)
        );
    }

    return sjcl.codec.hex.toBits(
        Array.from(uint8)
            .map(b => b.toString(16).padStart(2, "0"))
            .join("")
    );
}

Более корректный вариант через hex-кодек SJCL:

function arrayBufferToBitArray(buffer) {
    const bytes = new Uint8Array(buffer);
    let hex = "";
    for (let i = 0; i < bytes.length; i++) {
        hex += bytes[i].toString(16).padStart(2, "0");
    }
    return sjcl.codec.hex.toBits(hex);
}

SJCL → ArrayBuffer

function bitArrayToArrayBuffer(bitArray) {
    const hex = sjcl.codec.hex.fromBits(bitArray);

    const bytes = new Uint8Array(hex.length / 2);

    for (let i = 0; i < hex.length; i += 2) {
        bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
    }

    return bytes.buffer;
}

Пример гибридного шифрования: SubtleCrypto + SJCL

Сценарий

  • SubtleCrypto генерирует ключ AES-GCM
  • SJCL используется для постобработки или совместимости
  • Данные конвертируются между форматами

Шифрование через SubtleCrypto

async function encryptWithSubtle(key, plaintext) {
    const enc = new TextEncoder();
    const iv = crypto.getRandomValues(new Uint8Array(12));

    const ciphertext = await crypto.subtle.encrypt(
        {
            name: "AES-GCM",
            iv
        },
        key,
        enc.encode(plaintext)
    );

    return { ciphertext, iv };
}

Преобразование результата в SJCL

function convertEncryptedToSJCL(ciphertext) {
    return arrayBufferToBitArray(ciphertext);
}

Обратная совместимость: SJCL → SubtleCrypto

SJCL-данные могут быть использованы в SubtleCrypto только после конвертации:

async function importSJCLKeyForSubtle(sjclKeyBits) {
    const raw = bitArrayToArrayBuffer(sjclKeyBits);

    return crypto.subtle.importKey(
        "raw",
        raw,
        "AES-GCM",
        true,
        ["encrypt", "decrypt"]
    );
}

PBKDF2: совместное использование реализаций

SubtleCrypto PBKDF2

async function deriveKey(password, salt) {
    const enc = new TextEncoder();

    const baseKey = await crypto.subtle.importKey(
        "raw",
        enc.encode(password),
        "PBKDF2",
        false,
        ["deriveKey"]
    );

    return crypto.subtle.deriveKey(
        {
            name: "PBKDF2",
            salt: enc.encode(salt),
            iterations: 100000,
            hash: "SHA-256"
        },
        baseKey,
        { name: "AES-GCM", length: 256 },
        true,
        ["encrypt", "decrypt"]
    );
}

SJCL PBKDF2

const keyBits = sjcl.misc.pbkdf2("password", "salt", 100000, 256);

Совмещение PBKDF2 результатов

SubtleCrypto → SJCL:

const keyBuffer = await crypto.subtle.exportKey("raw", cryptoKey);
const sjclKey = arrayBufferToBitArray(keyBuffer);

SJCL → SubtleCrypto:

const buffer = bitArrayToArrayBuffer(sjclKey);
const cryptoKey = await crypto.subtle.importKey(
    "raw",
    buffer,
    "AES-GCM",
    true,
    ["encrypt", "decrypt"]
);

Практическая архитектура гибридной системы

Когда используется SubtleCrypto

  • AES-GCM (современный стандарт)
  • ECDSA / RSA
  • PBKDF2 / HKDF
  • Работа в браузере с высокой производительностью

Когда используется SJCL

  • Легаси-совместимость
  • Старые схемы AES-CCM/OCB
  • Нестандартные bitArray операции
  • Клиентские библиотеки без WebCrypto

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

1. Неправильная кодировка данных

SubtleCrypto всегда использует бинарные буферы, тогда как SJCL ожидает bitArray.

Ошибка:

sjcl.encrypt(key, arrayBuffer); // некорректно

2. Потеря IV

SubtleCrypto требует явного хранения IV:

{ ciphertext, iv }

SJCL часто инкапсулирует IV внутри структуры.


3. Несовпадение алгоритмов

SJCL AES ≠ SubtleCrypto AES-GCM:

  • SJCL AES-CCM / OCB → не совместим напрямую
  • SubtleCrypto AES-GCM → требует tag authentication

Рекомендованная схема интеграции

Унифицированный слой

const CryptoBridge = {
    toSJCL(buffer) {
        return arrayBufferToBitArray(buffer);
    },

    toSubtle(bits) {
        return bitArrayToArrayBuffer(bits);
    }
};

Пример гибридного шифрования

async function hybridEncrypt(cryptoKey, message) {
    const enc = new TextEncoder();

    const iv = crypto.getRandomValues(new Uint8Array(12));

    const encrypted = await crypto.subtle.encrypt(
        { name: "AES-GCM", iv },
        cryptoKey,
        enc.encode(message)
    );

    return {
        iv: CryptoBridge.toSJCL(iv.buffer),
        data: CryptoBridge.toSJCL(encrypted)
    };
}

Производительность и архитектурные выводы

SubtleCrypto выполняет операции:

  • на уровне браузерного движка
  • с аппаратным ускорением (AES-NI)

SJCL:

  • полностью интерпретируемый JS
  • гибкий, но медленный

Оптимальная стратегия:

  • SubtleCrypto → основной слой криптографии
  • SJCL → слой совместимости и обработки данных

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

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

Такая архитектура позволяет использовать преимущества обеих систем без потери совместимости и без нарушения криптографической корректности.