Криптографическая библиотека TweetNaCl.js реализует компактный набор проверенных примитивов из NaCl: публично-ключевое шифрование (box), симметричное шифрование (secretbox), подписи и хеширование. При построении защищённого канала поверх этих примитивов ключевой задачей становится не только шифрование сообщений, но и управление жизненным циклом ключей, включая их периодическую ротацию.
Защищённый канал в контексте TweetNaCl.js обычно строится на основе:
nacl.box.keyPair)nacl.box.after)Основная идея: стороны обмениваются публичными ключами, после чего вычисляют общий секрет, используемый для шифрования трафика.
import nacl from "tweetnacl";
import util from "tweetnacl-util";
const alice = nacl.box.keyPair();
const bob = nacl.box.keyPair();
const sharedKeyAlice = nacl.box.before(bob.publicKey, alice.secretKey);
const sharedKeyBob = nacl.box.before(alice.publicKey, bob.secretKey);
sharedKeyAlice и sharedKeyBob совпадают и
используются как симметрический ключ.
После установки общего ключа используется
nacl.box.after. Каждый пакет должен иметь уникальный nonce,
иначе безопасность нарушается.
function encryptMessage(sharedKey, message, nonce) {
const msgUint8 = util.decodeUTF8(message);
const box = nacl.box.after(msgUint8, nonce, sharedKey);
return util.encodeBase64(box);
}
function decryptMessage(sharedKey, boxMessage, nonce) {
const boxUint8 = util.decodeBase64(boxMessage);
const msg = nacl.box.open.after(boxUint8, nonce, sharedKey);
return util.encodeUTF8(msg);
}
Nonce обязан быть уникальным для каждого сообщения в рамках одного ключа. Обычно используется счётчик, синхронизированный между сторонами.
Использование одного sharedKey на протяжении всей сессии
приводит к нескольким рискам:
Решение — ротация ключей.
Ротация ключей в TweetNaCl.js не встроена на уровне протокола,
поэтому реализуется прикладным уровнем. Основная цель — периодически
обновлять sharedKey без разрыва соединения.
Существует несколько подходов:
Периодически стороны генерируют новые ключевые пары и выполняют повторный обмен:
function rotateKeys() {
return nacl.box.keyPair();
}
Процесс:
sharedKeyНедостаток: требует координации и синхронизации состояния.
Более стабильный подход — вывод новых ключей из текущего состояния.
function deriveKey(oldKey, counter) {
const input = util.decodeUTF8(oldKey + ":" + counter);
return nacl.hash(input).slice(0, 32);
}
Каждая сторона увеличивает счётчик и получает новый симметрический ключ.
Преимущества:
Недостатки:
На практике чаще используется комбинация:
box.beforeclass SecureChannel {
constructor(ownKeyPair, remotePublicKey) {
this.keyPair = ownKeyPair;
this.remotePublicKey = remotePublicKey;
this.sharedKey = nacl.box.before(remotePublicKey, ownKeyPair.secretKey);
this.sendCounter = 0;
this.recvCounter = 0;
}
createNonce(counter) {
const nonce = new Uint8Array(24);
const view = new DataView(nonce.buffer);
view.setUint32(0, counter);
return nonce;
}
}
send(message) {
const nonce = this.createNonce(this.sendCounter++);
const box = nacl.box.after(
util.decodeUTF8(message),
nonce,
this.sharedKey
);
return {
nonce: util.encodeBase64(nonce),
payload: util.encodeBase64(box)
};
}
receive(packet) {
const nonce = util.decodeBase64(packet.nonce);
const box = util.decodeBase64(packet.payload);
const msg = nacl.box.open.after(
box,
nonce,
this.sharedKey
);
this.recvCounter++;
return util.encodeUTF8(msg);
}
Добавляется отдельный слой управления ключами.
rotateSessionKey() {
const newKeyPair = nacl.box.keyPair();
this.sharedKey = nacl.box.before(
this.remotePublicKey,
newKeyPair.secretKey
);
this.keyPair = newKeyPair;
this.sendCounter = 0;
this.recvCounter = 0;
}
При ротации важно:
TweetNaCl.js предоставляет низкоуровневые примитивы, но не реализует полноценный протокол типа Signal. Поэтому:
Forward secrecy достигается только при частой генерации новых ключевых пар и уничтожении старых секретных ключей.
Типовая схема защищённого канала с ротацией:
Установка initial keypair
Вычисление shared key через box.before
Шифрование сообщений через box.after
Периодическая ротация:
Уничтожение старого состояния
Nonce должен быть строго уникальным в рамках конкретного ключа.
При смене ключа:
Надёжная практика — связывать nonce с:
Даже при смене сообщений повтор nonce с тем же ключом разрушает безопасность схемы.
Если одна сторона уже сменила ключ, а другая продолжает использовать старый, возникает рассинхронизация шифрования.
Сохранение предыдущих secretKey увеличивает поверхность
атаки и снижает эффект ротации.
Обобщённая модель выглядит как:
Такая схема позволяет приблизиться к свойствам современных протоколов защищённой связи даже при использовании минималистичных примитивов TweetNaCl.js.