Эфемерные ключи и forward secrecy

Эфемерные ключи — это временные криптографические ключи, создаваемые на короткий срок и используемые, как правило, в рамках одной сессии или даже одного сообщения. В отличие от долговременных (static) ключей, они не сохраняются после завершения взаимодействия и не используются повторно.

В библиотеке TweetNaCl.js эфемерные ключи создаются стандартными средствами генерации ключевых пар:

const nacl = require('tweetnacl');

const ephemeralKeyPair = nacl.box.keyPair();

Каждый вызов keyPair() генерирует новую пару:

  • publicKey — может быть передан другой стороне
  • secretKey — должен оставаться строго конфиденциальным

Ключевой особенностью является отсутствие повторного использования. Это критически важно для обеспечения криптографической стойкости.


Forward Secrecy (прямая секретность)

Forward secrecy — это свойство криптографической системы, при котором компрометация долговременного секретного ключа не позволяет расшифровать ранее перехваченные сообщения.

Это достигается за счёт:

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

Даже если злоумышленник получит доступ к приватному ключу спустя время, он не сможет восстановить сессионные ключи, использованные ранее.


Реализация в TweetNaCl.js

В TweetNaCl.js механизм обмена ключами и шифрования реализован через nacl.box, который использует:

  • алгоритм Curve25519 для обмена ключами
  • XSalsa20 для шифрования
  • Poly1305 для аутентификации

Базовый сценарий с эфемерными ключами

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

const aliceEphemeral = nacl.box.keyPair();
const bobEphemeral = nacl.box.keyPair();

Для шифрования сообщения используется комбинация:

  • приватного ключа отправителя
  • публичного ключа получателя
const nonce = nacl.randomBytes(nacl.box.nonceLength);

const messageUint8 = new TextEncoder().encode("secret message");

const encrypted = nacl.box(
  messageUint8,
  nonce,
  bobEphemeral.publicKey,
  aliceEphemeral.secretKey
);

Расшифровка выполняется с использованием:

  • приватного ключа получателя
  • публичного ключа отправителя
const decrypted = nacl.box.open(
  encrypted,
  nonce,
  aliceEphemeral.publicKey,
  bobEphemeral.secretKey
);

Предварительное вычисление общего секрета

Для повышения производительности при множественных операциях между одними и теми же ключами используется nacl.box.before:

const sharedKey = nacl.box.before(
  bobEphemeral.publicKey,
  aliceEphemeral.secretKey
);

После этого можно использовать:

  • nacl.box.after для шифрования
  • nacl.box.open.after для расшифровки
const encrypted = nacl.box.after(messageUint8, nonce, sharedKey);

Однако при использовании эфемерных ключей это преимущество минимально, так как ключи не переиспользуются.


Комбинирование статических и эфемерных ключей

На практике часто используется гибридный подход:

  • долговременные ключи — для аутентификации
  • эфемерные ключи — для сессионного обмена

Схема:

  1. У каждой стороны есть постоянная ключевая пара
  2. Для каждой сессии генерируются эфемерные ключи
  3. Выполняется обмен эфемерными публичными ключами
  4. Вычисляется общий секрет

Это позволяет:

  • удостовериться в личности сторон (через static ключи)
  • обеспечить forward secrecy (через ephemeral ключи)

Одноразовые ключи (One-time keys)

Эфемерные ключи могут использоваться не только на уровне сессии, но и на уровне сообщений.

Пример:

  • для каждого сообщения генерируется новая ключевая пара
  • сообщение шифруется с использованием этой пары
  • публичный ключ передаётся вместе с сообщением
const oneTime Key = nacl.box.keyPair();

const encrypted = nacl.box(
  messageUint8,
  nonce,
  recipientPublicKey,
  oneTimeKey.secretKey
);

Получатель использует переданный публичный ключ:

const decrypted = nacl.box.open(
  encrypted,
  nonce,
  oneTimeKey.publicKey,
  recipientSecretKey
);

Управление жизненным циклом ключей

Критические аспекты:

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

В JavaScript нет прямого контроля над очисткой памяти, поэтому рекомендуется:

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

Угрозы при неправильной реализации

Повторное использование эфемерных ключей Сводит на нет forward secrecy и может привести к компрометации сообщений.

Использование слабых генераторов случайных чисел TweetNaCl.js использует криптографически стойкий генератор, но при работе в нестандартных окружениях это требует проверки.

Хранение приватных ключей Даже временные ключи не должны записываться в лог или кэшироваться.


Практическая модель: защищённый чат

Типичный поток:

  1. Генерация эфемерных ключей для каждой сессии
  2. Обмен публичными ключами
  3. Вычисление общего секрета
  4. Шифрование сообщений с использованием этого секрета
  5. Уничтожение ключей после завершения

Такой подход:

  • защищает прошлые сообщения
  • минимизирует ущерб при компрометации
  • соответствует современным стандартам (например, протоколы типа Double Ratchet)

Ограничения TweetNaCl.js

  • отсутствует встроенная реализация протоколов с автоматической ротацией ключей
  • нет механизма хранения состояний сессий
  • forward secrecy достигается только при правильном использовании на уровне приложения

Это означает, что:

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

Рекомендации по проектированию

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

Связь с протоколами более высокого уровня

Эфемерные ключи в TweetNaCl.js могут служить основой для:

  • протоколов обмена ключами (ECDH)
  • защищённых мессенджеров
  • end-to-end шифрования

Однако такие механизмы, как:

  • Double Ratchet
  • X3DH

должны реализовываться поверх библиотеки, поскольку TweetNaCl.js не предоставляет их из коробки.