В библиотеке TweetNaCl.js механизм шифрования на основе
box строится вокруг концепции общего секрета
(shared key), получаемого через операцию умножения на эллиптической
кривой Curve25519. Этот секрет вычисляется из:
Функция nacl.box.before(theirPublicKey, mySecretKey)
возвращает именно этот shared key, который затем используется в
симметричном шифровании.
По сути:
box = вычисление shared key + симметричное
шифрованиеbox.before = только вычисление shared keyКаждый вызов nacl.box() включает дорогостоящую операцию
вычисления shared key. При интенсивном обмене сообщениями между одними и
теми же участниками это становится узким местом:
Идея заключается в том, чтобы вычислить shared key один раз и переиспользовать его:
const sharedKey = nacl.box.before(theirPublicKey, mySecretKey);
Далее вместо nacl.box используется:
nacl.box.after(message, nonce, sharedKey);
И для расшифровки:
nacl.box.open.after(ciphertext, nonce, sharedKey);
Если два участника обмениваются большим количеством сообщений в рамках одной сессии:
В таких сценариях:
Системы, где сообщения отправляются десятки или сотни раз в секунду:
Повторное вычисление shared key становится неоправданной нагрузкой.
Среды с ограниченной вычислительной мощностью:
Кэширование снижает энергопотребление и задержки.
Если сервер обслуживает множество клиентов, но каждый клиент взаимодействует длительное время:
Если взаимодействие происходит один раз:
Кэширование не даёт выгоды и усложняет код.
Если используется ротация ключей:
В таких случаях shared key быстро устаревает и кэш становится бесполезным или даже опасным.
Хранение shared key увеличивает поверхность атаки:
Особенно критично:
Shared key не должен храниться бесконечно:
В JavaScript это сложно гарантировать, но рекомендуется:
sharedKey.fill(0);
после завершения работы.
В случае нескольких собеседников требуется структура хранения:
const sharedKeyCache = new Map();
Ключом может быть:
Пример:
function getSharedKey(theirPublicKey, mySecretKey) {
const keyId = Buffer.from(theirPublicKey).toString('hex');
if (!sharedKeyCache.has(keyId)) {
const sharedKey = nacl.box.before(theirPublicKey, mySecretKey);
sharedKeyCache.set(keyId, sharedKey);
}
return sharedKeyCache.get(keyId);
}
Ключевой аспект — управление жизненным циклом:
Пример:
sharedKeyCache.delete(keyId);
| Характеристика | nacl.box | nacl.box.after |
|---|---|---|
| Простота | высокая | требует подготовки |
| Производительность | ниже | выше |
| Повторное использование | нет | да |
| Подходит для | разовых операций | потоковых сценариев |
Типичный сценарий:
box.afterДаже при кэшировании shared key:
Shared key должен существовать только в оперативной памяти.
Если ключи обновляются, но кэш не очищается:
Кэширование shared key — это оптимизация, оправданная при:
Но оно требует дисциплины:
В противном случае выгода в производительности может быть нивелирована снижением безопасности.