Передача криптографических ключей между вкладками браузера опирается
на сочетание возможностей 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 без ручной
сериализации.
Механизм обмена между вкладками базируется на
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("секретные данные")
);
}
});
При передаче через postMessage браузер:
CryptoKeyusages,
algorithm)Важно, что фактический криптографический материал не покидает изолированное хранилище браузера.
Несмотря на удобство, существуют важные ограничения:
1. Ограничение origin
Передача возможна только внутри одного источника. Любая попытка переслать ключ между разными доменами блокируется политикой безопасности.
2. Жизненный цикл контекста
Переданный CryptoKey остаётся валидным только в рамках
текущей сессии браузера. После перезагрузки страницы или закрытия
вкладки ключ утрачивается.
3. Зависимость от поддержки алгоритма
Если принимающий контекст не поддерживает алгоритм, связанный с ключом, операция с ним завершится ошибкой.
Web Crypto API предоставляет метод exportKey, но его
использование принципиально отличается от передачи через
postMessage.
Экспорт:
const raw = await crypto.subtle.exportKey("raw", key);
Передача через postMessage:
postMessage({ key });
Различия:
postMessage передаёт внутренний объект
CryptoKeyextractable: truepostMessage не требует извлечения материала ключаДля 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, который уменьшает риск случайного утекания
сообщений в другие обработчики.
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 остаётся:
usages)Изменить свойства ключа после передачи невозможно. Единственный
способ модификации — создание нового ключа через
subtle.generateKey или subtle.importKey.
Механизм идентичен 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 и не теряет криптографических свойств при копировании между контекстами.
Хотя CryptoKey может быть передан без экспорта, параметр
extractable влияет на возможность дальнейшего извлечения
материала:
extractable: false — ключ нельзя экспортировать через
exportKeyЭто создаёт важное разделение:
crypto.subtle.generateKeypostMessageМеханизм передачи CryptoKey между вкладками представляет собой сочетание безопасной изоляции Web Crypto API и встроенного механизма межконтекстного обмена JavaScript-объектами, где ключ остаётся управляемым объектом браузера, а не переносимым набором байтов.