Передача ключей между вкладками через постсообщения

Передача криптографических ключей между вкладками браузера опирается на сочетание возможностей Web Crypto API и механизма межконтекстного обмена сообщениями, основанного на postMessage и структурированном клонировании данных. Внутри одного origin (домен, протокол, порт) браузер позволяет безопасно пересылать объекты CryptoKey между контекстами без необходимости сериализации в строку или вручную извлекать материал ключа.

Объект CryptoKey не является обычной структурой данных. Он представляет собой ссылку на ключевой материал, который хранится внутри криптографического подсистемного слоя браузера и недоступен напрямую в JavaScript.

Ключевые свойства:

  • type — тип ключа ("secret", "public", "private")
  • extractable — определяет возможность экспорта ключевого материала
  • algorithm — используемый алгоритм (AES-GCM, RSA-OAEP и др.)
  • usages — разрешённые операции (encrypt, decrypt, sign, verify, deriveKey)

При передаче через postMessage используется структурированное клонирование (Structured Clone Algorithm). В этом механизме CryptoKey поддерживается как специальный тип, что позволяет передавать ключи между окнами, вкладками и iframes без ручной сериализации.

Передача CryptoKey через postMessage

Механизм обмена между вкладками базируется на window.postMessage, который позволяет отправлять сообщения в другой контекст исполнения.

// вкладка-отправитель
async function createAndSendKey(targetWindow) {
  const key = await crypto.subtle.generateKey(
    {
      name: "AES-GCM",
      length: 256
    },
    true,
    ["encrypt", "decrypt"]
  );

  targetWindow.postMessage(
    {
      type: "CRYPTO_KEY_TRANSFER",
      key
    },
    window.origin
  );
}

В данном случае объект key передаётся напрямую без вызова exportKey. Браузер сам выполняет структурированное клонирование.

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

window.addEventListener("message", async (event) => {
  if (event.origin !== window.origin) return;

  if (event.data?.type === "CRYPTO_KEY_TRANSFER") {
    const key = event.data.key;

    const encrypted = await crypto.subtle.encrypt(
      {
        name: "AES-GCM",
        iv: crypto.getRandomValues(new Uint8Array(12))
      },
      key,
      new TextEncoder().encode("секретные данные")
    );
  }
});

Как работает структурированное клонирование CryptoKey

При передаче через postMessage браузер:

  • распознаёт объект как CryptoKey
  • не сериализует его в JSON
  • создаёт внутреннюю копию ссылки на ключевой материал
  • сохраняет ограничения использования (usages, algorithm)

Важно, что фактический криптографический материал не покидает изолированное хранилище браузера.

Ограничения передачи ключей

Несмотря на удобство, существуют важные ограничения:

1. Ограничение origin

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

2. Жизненный цикл контекста

Переданный CryptoKey остаётся валидным только в рамках текущей сессии браузера. После перезагрузки страницы или закрытия вкладки ключ утрачивается.

3. Зависимость от поддержки алгоритма

Если принимающий контекст не поддерживает алгоритм, связанный с ключом, операция с ним завершится ошибкой.

Отличие передачи ключа от экспорта

Web Crypto API предоставляет метод exportKey, но его использование принципиально отличается от передачи через postMessage.

Экспорт:

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

Передача через postMessage:

postMessage({ key });

Различия:

  • экспорт создаёт внешний байтовый формат (например, ArrayBuffer, JWK)
  • postMessage передаёт внутренний объект CryptoKey
  • экспорт требует extractable: true
  • postMessage не требует извлечения материала ключа

Передача симметричных и асимметричных ключей

Симметричные ключи

Для AES-ключей передача происходит напрямую:

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

postMessage({ key });

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

Публичные ключи

Публичные ключи RSA или ECDSA могут свободно распространяться между вкладками:

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

postMessage({ publicKey: keyPair.publicKey });

Приватные ключи

Передача приватных ключей возможна технически, но требует строгого контроля безопасности:

postMessage({ privateKey: keyPair.privateKey });

Даже при возможности передачи браузер не делает различий на уровне структурированного клонирования — ограничение определяется политикой приложения.

Использование MessageChannel вместо postMessage

Для изолированной двусторонней коммуникации часто используется MessageChannel, который уменьшает риск случайного утекания сообщений в другие обработчики.

const channel = new MessageChannel();

otherWindow.postMessage(
  { type: "INIT_PORT", port: channel.port1 },
  window.origin,
  [channel.port1]
);

На принимающей стороне:

window.addEventListener("message", (event) => {
  const port = event.data.port;

  port.onmess age = async (e) => {
    const key = e.data.key;
  };
});

Передача CryptoKey через канал работает аналогично postMessage, но с выделенным каналом связи.

Безопасность передачи ключей между вкладками

Передача криптографических объектов между контекстами требует строгого соблюдения модели доверия внутри origin.

Основные риски:

1. XSS в любой вкладке

Если одна вкладка скомпрометирована, переданные ключи могут быть перехвачены через postMessage-listener.

2. Перехват сообщений

Непроверенный event.origin позволяет злоумышленнику внедриться в поток сообщений.

3. Утечка через глобальные слушатели

События message доступны всем скриптам страницы.

Минимальный контроль безопасности:

window.addEventListener("message", (event) => {
  if (event.origin !== "https://trusted.example") return;
  if (!event.data || event.data.type !== "CRYPTO_KEY_TRANSFER") return;
});

Клонирование CryptoKey и неизменяемость

Переданный CryptoKey остаётся:

  • неизменяемым (immutable)
  • привязанным к алгоритму
  • ограниченным списком операций (usages)

Изменить свойства ключа после передачи невозможно. Единственный способ модификации — создание нового ключа через subtle.generateKey или subtle.importKey.

Передача ключей в iframe

Механизм идентичен postMessage, но добавляется уровень вложенности контекста:

iframe.contentWindow.postMessage({ key }, window.origin);

Важным является контроль origin iframe, так как он становится полноценным участником криптографического обмена.

Практическая модель распределённого ключа между вкладками

Типичный сценарий:

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

Пример распределённой модели:

// генерация в главной вкладке
const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
);

broadcastChannel.postMessage({ key });

Хотя здесь используется BroadcastChannel, принцип передачи CryptoKey аналогичен postMessage.

Поведение при копировании контекста

При открытии новой вкладки через window.open ключи могут быть переданы сразу:

const child = window.open("/child.html");

child.onl oad = () => {
  child.postMessage({ key }, window.origin);
};

Ключ остаётся связанным с origin и не теряет криптографических свойств при копировании между контекстами.

Особенности работы с extractable ключами

Хотя CryptoKey может быть передан без экспорта, параметр extractable влияет на возможность дальнейшего извлечения материала:

  • extractable: false — ключ нельзя экспортировать через exportKey
  • но он всё равно может передаваться между вкладками

Это создаёт важное разделение:

  • передача объекта возможна
  • извлечение байтового представления может быть запрещено

Итоговая модель жизненного цикла ключа при передаче

  • генерация в одном контексте через crypto.subtle.generateKey
  • структурированное клонирование при postMessage
  • использование в другом контексте без доступа к сырому материалу
  • невозможность восстановления ключа вне браузерной сессии

Механизм передачи CryptoKey между вкладками представляет собой сочетание безопасной изоляции Web Crypto API и встроенного механизма межконтекстного обмена JavaScript-объектами, где ключ остаётся управляемым объектом браузера, а не переносимым набором байтов.