nacl.box.before: вычисление shared key

Функция nacl.box.before предназначена для предварительного вычисления общего секретного ключа (shared key) на основе пары ключей: приватного ключа одной стороны и публичного ключа другой стороны. Этот общий ключ затем используется в симметричных операциях шифрования и расшифрования, что позволяет значительно повысить производительность при множественных обменах сообщениями между одними и теми же участниками.

В основе лежит схема обмена ключами Curve25519, применяемая в алгоритме crypto_box. Вместо повторного выполнения дорогой операции скалярного умножения при каждом вызове nacl.box, общий ключ вычисляется один раз и далее переиспользуется.


Сигнатура функции

nacl.box.before(theirPublicKey, mySecretKey)

Аргументы:

  • theirPublicKeyUint8Array(32) — публичный ключ другой стороны
  • mySecretKeyUint8Array(32) — приватный ключ текущей стороны

Возвращаемое значение:

  • Uint8Array(32) — общий секретный ключ (shared key)

Криптографическая основа

Функция реализует операцию:

sharedKey = scalarMult(mySecretKey, theirPublicKey)

Это классическая схема Диффи–Хеллмана на эллиптической кривой Curve25519. Важное свойство:

scalarMult(A_secret, B_public) === scalarMult(B_secret, A_public)

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


Связь с другими функциями

nacl.box.before используется совместно с:

  • nacl.box.after — шифрование с уже вычисленным shared key
  • nacl.box.open.after — расшифрование с shared key

Эквивалент:

nacl.box(message, nonce, theirPublicKey, mySecretKey)

равносилен:

const sharedKey = nacl.box.before(theirPublicKey, mySecretKey);
nacl.box.after(message, nonce, sharedKey);

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

Оптимизация при многократной передаче сообщений

Если между двумя сторонами происходит интенсивный обмен сообщениями, повторное вычисление shared key становится узким местом. Использование nacl.box.before позволяет:

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

Пример использования

import nacl from 'tweetnacl';

// Генерация ключевых пар
const alice = nacl.box.keyPair();
const bob = nacl.box.keyPair();

// Предварительное вычисление общего ключа
const aliceShared = nacl.box.before(bob.publicKey, alice.secretKey);
const bobShared = nacl.box.before(alice.publicKey, bob.secretKey);

// Проверка равенства
console.log(
  nacl.verify(aliceShared, bobShared) // true
);

Шифрование с использованием shared key

const message = new TextEncoder().encode("Привет");
const nonce = nacl.randomBytes(nacl.box.nonceLength);

// Alice шифрует
const encrypted = nacl.box.after(message, nonce, aliceShared);

// Bob расшифровывает
const decrypted = nacl.box.open.after(encrypted, nonce, bobShared);

const text = new TextDecoder().decode(decrypted);
console.log(text); // "Привет"

Важные особенности

1. Размер ключа

  • Всегда 32 байта (Uint8Array(32))

2. Детерминированность

  • Для одной и той же пары ключей результат всегда одинаковый

3. Безопасность

  • Shared key не должен передаваться по сети
  • Должен храниться только в памяти
  • Желательно очищать после использования

4. Невозможность восстановления приватных ключей

Даже имея shared key, невозможно восстановить ни один из приватных ключей благодаря свойствам эллиптической криптографии.


Когда использовать nacl.box.before

Использование оправдано в следующих сценариях:

  • постоянное соединение между клиентом и сервером
  • peer-to-peer взаимодействие
  • защищённые чаты
  • протоколы сессий

Необязательно использовать, если сообщение передаётся однократно.


Отличие от nacl.scalarMult

Функция nacl.scalarMult выполняет ту же базовую операцию, но:

  • возвращает «сырой» результат без дополнительной обработки
  • не предназначена напрямую для использования в box

nacl.box.before:

  • применяет внутреннюю нормализацию
  • возвращает ключ, пригодный для box.after

Ошибки и подводные камни

Неправильный порядок аргументов

// Неправильно
nacl.box.before(mySecretKey, theirPublicKey)

Несовпадение ключей

Использование ключей от разных пар приведёт к несовпадающим shared key и невозможности расшифрования.

Повторное использование nonce

Хотя это не связано напрямую с before, при использовании box.after необходимо гарантировать уникальность nonce для каждого сообщения.


Внутренний механизм

В TweetNaCl:

  1. Выполняется crypto_scalarmult
  2. Результат пропускается через HSalsa20
  3. Формируется ключ для XSalsa20-Poly1305

Это означает, что shared key — не просто результат умножения, а уже подготовленный симметричный ключ.


Производительность

Основной выигрыш:

Операция Стоимость
scalarMult высокая
symmetric encryption низкая

nacl.box.before позволяет выполнить дорогую операцию один раз.


Архитектурный паттерн

Типичная схема:

  1. Обмен публичными ключами
  2. Вычисление shared key через before
  3. Сохранение shared key в сессии
  4. Использование box.after / open.after для всех сообщений

Ограничения

  • Не подходит для хранения на диске без дополнительной защиты
  • Не обеспечивает аутентификацию сам по себе — она встроена в box.after
  • Требует строгого контроля ключей и nonce

Минимальный шаблон

function createSharedKey(mySecret, theirPublic) {
  return nacl.box.before(theirPublic, mySecret);
}

function encrypt(msg, nonce, sharedKey) {
  return nacl.box.after(msg, nonce, sharedKey);
}

function decrypt(cipher, nonce, sharedKey) {
  return nacl.box.open.after(cipher, nonce, sharedKey);
}

Резюме ключевых свойств

  • устраняет повторные вычисления Curve25519
  • возвращает готовый симметричный ключ
  • совместим только с box.after и open.after
  • критичен для высоконагруженных криптографических систем