Гибридное шифрование: RSA + AES на практике

В прикладной криптографии почти всегда используется сочетание асимметричных и симметричных алгоритмов. Причина проста: асимметричные алгоритмы (RSA, ECDH) удобны для обмена ключами, но слишком медленные для шифрования больших объёмов данных. Симметричные алгоритмы (AES-GCM, AES-CBC) быстрые, но требуют безопасной доставки ключа.

Гибридная схема объединяет сильные стороны обоих подходов:

  • RSA используется для защиты ключа
  • AES используется для шифрования данных

Web Crypto API реализует все необходимые примитивы на уровне браузера через объект crypto.subtle.


Базовые криптографические примитивы Web Crypto API

Ключевая точка входа — window.crypto.subtle. Этот интерфейс предоставляет асинхронные операции:

  • генерация ключей
  • шифрование и дешифрование
  • экспорт и импорт ключей
  • вычисление подписей и хэшей

Особенность API — строгая работа с ArrayBuffer и TypedArray, что исключает небезопасные преобразования строк.


Генерация RSA ключевой пары

RSA в Web Crypto используется чаще всего в режиме RSA-OAEP, который предназначен именно для шифрования ключей.

const rsaKeyPair = await crypto.subtle.generateKey(
  {
    name: "RSA-OAEP",
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256",
  },
  true,
  ["encrypt", "decrypt"]
);

Ключевые параметры:

  • modulusLength — длина ключа, минимум 2048 бит
  • publicExponent — стандартное значение 65537
  • hash — хэш-функция для OAEP padding

Результат — объект с publicKey и privateKey.


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

Для шифрования данных используется AES-GCM как наиболее безопасный и современный режим.

const aesKey = await crypto.subtle.generateKey(
  {
    name: "AES-GCM",
    length: 256,
  },
  true,
  ["encrypt", "decrypt"]
);

AES-GCM обеспечивает:

  • конфиденциальность
  • целостность данных
  • встроенную аутентификацию (tag)

Экспорт AES ключа для шифрования RSA

Чтобы передать AES-ключ через RSA, его нужно экспортировать в бинарный формат:

const rawAesKey = await crypto.subtle.exportKey("raw", aesKey);

На этом этапе ключ становится обычным ArrayBuffer, который можно зашифровать RSA.


Шифрование AES ключа с помощью RSA-OAEP

RSA используется для шифрования небольших данных — в данном случае AES ключа.

const encryptedAesKey = await crypto.subtle.encrypt(
  {
    name: "RSA-OAEP",
  },
  rsaKeyPair.publicKey,
  rawAesKey
);

Результат encryptedAesKey можно безопасно передавать по сети или сохранять вместе с зашифрованными данными.


Шифрование данных с использованием AES-GCM

Перед шифрованием необходимо создать уникальный nonce (IV). В AES-GCM это критически важно: повтор IV полностью ломает безопасность.

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

const encodedData = new TextEncoder().encode("Секретное сообщение");

const encryptedData = await crypto.subtle.encrypt(
  {
    name: "AES-GCM",
    iv: iv,
  },
  aesKey,
  encodedData
);

Важно:

  • IV должен быть случайным
  • IV можно хранить открыто
  • длина IV обычно 12 байт

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

Процесс шифрования данных выглядит следующим образом:

  1. Генерация RSA ключевой пары

  2. Генерация AES ключа

  3. Экспорт AES ключа

  4. Шифрование AES ключа через RSA public key

  5. Шифрование данных через AES-GCM

  6. Передача:

    • зашифрованного AES ключа
    • IV
    • зашифрованного сообщения

Дешифрование AES ключа

На стороне получателя используется RSA private key:

const decryptedAesKeyRaw = await crypto.subtle.decrypt(
  {
    name: "RSA-OAEP",
  },
  rsaKeyPair.privateKey,
  encryptedAesKey
);

После этого необходимо импортировать ключ обратно в Web Crypto:

const decryptedAesKey = await crypto.subtle.importKey(
  "raw",
  decryptedAesKeyRaw,
  {
    name: "AES-GCM",
  },
  false,
  ["decrypt"]
);

Дешифрование данных AES-GCM

После восстановления AES ключа выполняется расшифровка:

const decryptedBuffer = await crypto.subtle.decrypt(
  {
    name: "AES-GCM",
    iv: iv,
  },
  decryptedAesKey,
  encryptedData
);

const decodedText = new TextDecoder().decode(decryptedBuffer);

Структура передаваемого сообщения

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

{
  "key": "encryptedAesKey (base64)",
  "iv": "base64",
  "data": "encryptedData (base64)"
}

Такой формат удобен для:

  • REST API
  • WebSocket сообщений
  • локального хранения

Кодирование ArrayBuffer в Base64

Web Crypto работает с бинарными данными, поэтому часто требуется преобразование:

function arrayBufferToBase64(buffer) {
  const bytes = new Uint8Array(buffer);
  let binary = "";
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

И обратное преобразование:

function base64ToArrayBuffer(base64) {
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes.buffer;
}

Причины использования AES-GCM вместо AES-CBC

AES-GCM стал стандартом благодаря встроенной аутентификации. В отличие от CBC:

  • не требует отдельного HMAC
  • защищает от подмены данных
  • быстрее на современных CPU
  • поддерживается аппаратным ускорением

Ошибки и уязвимости гибридной схемы

Даже при использовании Web Crypto API возможны критические ошибки:

Повтор IV в AES-GCM

  • приводит к утечке информации о plaintext

Использование RSA без OAEP

  • небезопасные схемы PKCS#1 v1.5 подвержены атакам

Слабый размер RSA

  • 1024 бит считается небезопасным

Отсутствие проверки целостности

  • если не используется GCM, возможна подмена данных

Практическая структура криптосистемы в приложении

В реальных веб-приложениях гибридное шифрование обычно встроено в слой передачи данных:

  • клиент генерирует AES ключ для сессии
  • сервер передаёт RSA public key
  • клиент шифрует AES ключ
  • все сообщения идут через AES-GCM

Такой подход снижает нагрузку на сервер и повышает масштабируемость.


Производительность и ограничения Web Crypto API

Web Crypto API работает асинхронно и часто использует нативные реализации:

  • операции выполняются вне main thread
  • доступ к ключам ограничен (non-extractable keys)
  • нет прямого доступа к сырому RSA-шифрованию без padding

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

  • нельзя использовать произвольные криптографические алгоритмы
  • невозможна ручная настройка padding схем
  • ключи нельзя легко сериализовать без экспорта

Особенности безопасности в браузере

Криптография в браузере зависит от окружения:

  • HTTPS обязателен
  • доступ к crypto.subtle отсутствует в insecure context
  • sandbox может ограничивать операции

Дополнительно:

  • приватные ключи могут быть non-extractable
  • браузер сам управляет хранением ключей в памяти

Типовая архитектура гибридного шифрования в Web-приложениях

На уровне архитектуры система выглядит так:

  • слой обмена ключами (RSA)
  • слой шифрования данных (AES-GCM)
  • слой сериализации (Base64/JSON)
  • транспорт (HTTPS/WebSocket)

Такая структура используется в мессенджерах, финтех-приложениях и системах защищённого документооборота.