Обмен открытыми ключами: безопасные каналы передачи

Ассиметричная криптография опирается на пару ключей: открытый (public key) и закрытый (secret key). Безопасность системы определяется тем, что открытый ключ можно распространять свободно, а закрытый должен оставаться строго конфиденциальным. Однако возникает фундаментальная проблема: как убедиться, что полученный открытый ключ действительно принадлежит заявленному владельцу.

Если злоумышленник перехватит канал передачи и подменит ключ, возможна атака типа «человек посередине» (MITM). В этом случае все последующие криптографические операции будут корректны с точки зрения алгоритмов, но полностью скомпрометированы.

Основные угрозы при передаче ключей

Подмена ключа

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

Повторная атака

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

Отсутствие аутентификации

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

Подходы к безопасному обмену ключами

1. Передача по доверенному каналу

Самый простой способ — передать открытый ключ через заранее защищённый канал:

  • физическая передача (USB, QR-код)
  • личная встреча
  • защищённая внутренняя сеть

Недостаток: плохо масштабируется.

2. Проверка отпечатка ключа

Открытый ключ хэшируется, и его короткий отпечаток (fingerprint) сравнивается по независимому каналу:

const hash = nacl.hash(publicKey);
const fingerprint = hash.slice(0, 8); // короткий отпечаток

Сравнение может происходить:

  • по телефону
  • через мессенджер
  • визуально (QR-коды)

3. Использование цифровых подписей

Открытый ключ подписывается доверенной стороной:

const signedKey = nacl.sign(publicKey, signingSecretKey);

Получатель проверяет подпись:

const verified = nacl.sign.open(signedKey, signingPublicKey);

Если подпись валидна — ключ подлинный.

4. Инфраструктура открытых ключей (PKI)

Использование сертификатов, подписанных удостоверяющим центром (CA). В контексте TweetNaCl это реализуется вручную или через внешние системы, поскольку библиотека минималистична.

Обмен ключами с использованием Diffie-Hellman (в TweetNaCl)

TweetNaCl реализует безопасный обмен секретами через функцию nacl.box, основанную на алгоритме Curve25519.

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

const alice = nacl.box.keyPair();
const bob = nacl.box.keyPair();

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

  • Alice отправляет alice.publicKey
  • Bob отправляет bob.publicKey

Важно: этот этап должен быть защищён от подмены.

Вычисление общего секрета

const sharedKeyAlice = nacl.box.before(bob.publicKey, alice.secretKey);
const sharedKeyBob = nacl.box.before(alice.publicKey, bob.secretKey);

Оба значения будут идентичны.

Использование общего секрета

const nonce = nacl.randomBytes(nacl.box.nonceLength);

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

const encrypted = nacl.box.after(message, nonce, sharedKeyAlice);

const decrypted = nacl.box.open.after(encrypted, nonce, sharedKeyBob);

Предварительное вычисление ключа (precomputation)

Функция nacl.box.before позволяет вычислить общий ключ заранее и использовать его многократно:

Преимущества:

  • повышение производительности
  • уменьшение вычислительной нагрузки

Недостатки:

  • требует безопасного хранения промежуточного ключа

Аутентифицированный обмен ключами

Чтобы исключить MITM, применяется схема:

  1. Генерация ключей для шифрования (nacl.box)
  2. Генерация ключей для подписи (nacl.sign)
  3. Подписание открытого ключа шифрования
const encryptionKeys = nacl.box.keyPair();
const signingKeys = nacl.sign.keyPair();

const signedPublicKey = nacl.sign(encryptionKeys.publicKey, signingKeys.secretKey);

Получатель:

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

Постоянные и эфемерные ключи

Постоянные (static keys)

  • используются долго
  • подходят для идентификации

Эфемерные (ephemeral keys)

  • создаются на одну сессию
  • повышают приватность
  • обеспечивают forward secrecy

Пример эфемерного обмена:

const ephemeral = nacl.box.keyPair();

Комбинация:

  • постоянный ключ (идентификация)
  • эфемерный ключ (сессия)

Forward Secrecy

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

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

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

Работа с nonce

Nonce должен быть:

  • уникальным
  • непредсказуемым
const nonce = nacl.randomBytes(24);

Повторное использование nonce с тем же ключом — критическая ошибка.

Каналы передачи открытых ключей

HTTPS

  • защищён от пассивного перехвата
  • уязвим при компрометации сертификатов

WebSocket + TLS

  • подходит для реального времени

QR-коды

  • удобны для офлайн-обмена

DNS (с подписями DNSSEC)

  • используется в некоторых системах

Проверка целостности ключей

Дополнительный уровень защиты:

const checksum = nacl.hash(publicKey);

Сравнение checksum:

  • вручную
  • автоматически

Практические рекомендации

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

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

  1. Генерация пары ключей
  2. Подписание открытого ключа
  3. Передача ключа + подписи
  4. Проверка подписи
  5. Вычисление общего секрета
  6. Шифрование сообщений

Такой подход устраняет основные уязвимости и делает обмен ключами устойчивым к активным атакам.