При сценариях, где один отправитель многократно взаимодействует с
одним и тем же получателем, наивное использование криптографических
примитивов приводит к избыточным вычислениям. В библиотеке TweetNaCl.js
основная нагрузка приходится на операции с публичными ключами (кривые
эллиптической криптографии), особенно при использовании
nacl.box.
Каждый вызов nacl.box по умолчанию включает:
XSalsa20-Poly1305Повторное вычисление общего секрета при каждом сообщении становится узким местом при высокой частоте обмена.
Для оптимизации предусмотрен механизм предвычисления общего ключа:
const sharedKey = nacl.box.before(theirPublicKey, mySecretKey);
Этот вызов выполняет дорогостоящую операцию один раз. Далее для всех сообщений используется:
const encrypted = nacl.box.after(message, nonce, sharedKey);
Аналогично для расшифровки:
const decrypted = nacl.box.open.after(encrypted, nonce, sharedKey);
sharedKey в памяти| Операция | Относительная стоимость |
|---|---|
nacl.box |
высокая |
nacl.box.before |
высокая (однократно) |
nacl.box.after |
низкая |
nacl.secretbox |
низкая |
Основной выигрыш достигается за счёт переноса тяжёлой операции
(before) вне цикла.
Даже при использовании предвычисленного ключа, каждый вызов
nacl.box.after требует уникального nonce.
const nonce = nacl.randomBytes(nacl.box.nonceLength);
При интенсивном обмене может использоваться счётчик:
function incrementNonce(nonce) {
for (let i = nonce.length - 1; i >= 0; i--) {
if (++nonce[i] !== 0) break;
}
}
Такой подход снижает нагрузку на генератор случайных чисел.
При высокой частоте операций значительную роль играет работа с памятью.
Uint8Array)Пример:
const messageBuffer = new Uint8Array(1024);
Переиспользование буфера вместо создания нового при каждом сообщении уменьшает давление на GC.
При отправке серии сообщений:
Однако:
В браузерной среде криптографические операции могут блокировать основной поток.
Пример архитектуры:
nacl.box.afterpostMessageЕсли обмен происходит в рамках уже установленного защищённого канала,
можно отказаться от box и перейти к
secretbox:
const encrypted = nacl.secretbox(message, nonce, sharedKey);
| Характеристика | box | secretbox |
|---|---|---|
| Тип ключа | асимметричный | симметричный |
| Производительность | ниже | выше |
| Удобство | выше (без обмена ключами) | требует ключа |
В сценарии «один адресат — много сообщений» после начального обмена
ключами предпочтительнее использовать secretbox.
При взаимодействии с несколькими получателями:
sharedKey для каждой парыMap<publicKey, sharedKey>const cache = new Map();
function getSharedKey(pubKey) {
if (!cache.has(pubKey)) {
cache.set(pubKey, nacl.box.before(pubKey, mySecretKey));
}
return cache.get(pubKey);
}
Алгоритм XSalsa20 работает как потоковый шифр,
поэтому:
Перед шифрованием данные часто преобразуются:
const messageUint8 = new TextEncoder().encode(message);
И обратно:
const message = new TextDecoder().decode(decrypted);
TextEncoder /
TextDecoderconst encoder = new TextEncoder();
const decoder = new TextDecoder();
Переиспользование экземпляров снижает накладные расходы.
Библиотека ориентирована на безопасность и компактность, а не на максимальную производительность:
При экстремальных нагрузках возможен переход на:
sharedKeynacl.box.after или
nacl.secretboxТакой подход позволяет уменьшить криптографическую нагрузку на порядок при сохранении уровня безопасности.