Ассиметричная криптография опирается на пару ключей: открытый (public key) и закрытый (secret key). Безопасность системы определяется тем, что открытый ключ можно распространять свободно, а закрытый должен оставаться строго конфиденциальным. Однако возникает фундаментальная проблема: как убедиться, что полученный открытый ключ действительно принадлежит заявленному владельцу.
Если злоумышленник перехватит канал передачи и подменит ключ, возможна атака типа «человек посередине» (MITM). В этом случае все последующие криптографические операции будут корректны с точки зрения алгоритмов, но полностью скомпрометированы.
Злоумышленник перехватывает открытый ключ и заменяет его своим. Получатель не замечает подмены и шифрует данные на подставной ключ.
Использование ранее перехваченных ключей или сообщений для имитации легитимного взаимодействия.
Простой обмен ключами без проверки подлинности не гарантирует безопасность даже при использовании надёжных алгоритмов.
Самый простой способ — передать открытый ключ через заранее защищённый канал:
Недостаток: плохо масштабируется.
Открытый ключ хэшируется, и его короткий отпечаток (fingerprint) сравнивается по независимому каналу:
const hash = nacl.hash(publicKey);
const fingerprint = hash.slice(0, 8); // короткий отпечаток
Сравнение может происходить:
Открытый ключ подписывается доверенной стороной:
const signedKey = nacl.sign(publicKey, signingSecretKey);
Получатель проверяет подпись:
const verified = nacl.sign.open(signedKey, signingPublicKey);
Если подпись валидна — ключ подлинный.
Использование сертификатов, подписанных удостоверяющим центром (CA). В контексте TweetNaCl это реализуется вручную или через внешние системы, поскольку библиотека минималистична.
TweetNaCl реализует безопасный обмен секретами через функцию
nacl.box, основанную на алгоритме Curve25519.
const alice = nacl.box.keyPair();
const bob = nacl.box.keyPair();
alice.publicKeybob.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);
Функция nacl.box.before позволяет вычислить общий ключ
заранее и использовать его многократно:
Преимущества:
Недостатки:
Чтобы исключить MITM, применяется схема:
nacl.box)nacl.sign)const encryptionKeys = nacl.box.keyPair();
const signingKeys = nacl.sign.keyPair();
const signedPublicKey = nacl.sign(encryptionKeys.publicKey, signingKeys.secretKey);
Получатель:
Пример эфемерного обмена:
const ephemeral = nacl.box.keyPair();
Комбинация:
Даже если закрытый ключ скомпрометирован в будущем, прошлые сообщения остаются защищёнными.
Достигается за счёт:
Nonce должен быть:
const nonce = nacl.randomBytes(24);
Повторное использование nonce с тем же ключом — критическая ошибка.
Дополнительный уровень защиты:
const checksum = nacl.hash(publicKey);
Сравнение checksum:
Такой подход устраняет основные уязвимости и делает обмен ключами устойчивым к активным атакам.