Библиотека TweetNaCl.js реализует криптографические примитивы поверх строгого, минималистичного API. Управление ключами начинается с их генерации, которая в большинстве сценариев опирается на встроенные функции:
import nacl from "tweetnacl";
// Для асимметричного шифрования (box)
const keyPair = nacl.box.keyPair();
// Для цифровой подписи
const signKeyPair = nacl.sign.keyPair();
Каждая ключевая пара представляет собой структуру из двух
Uint8Array:
publicKeysecretKeyКлючи имеют фиксированный размер:
box: 32 байтаsign: 64 байта для секретного ключа (включает публичную
часть)Ключи не сериализуются автоматически и не имеют встроенного механизма хранения, что требует явной работы с представлением данных.
Внутренний формат TweetNaCl — Uint8Array, однако для
хранения и передачи чаще используются текстовые представления.
function toBase64(u8) {
return btoa(String.fromCharCode(...u8));
}
function fromBase64(str) {
return new Uint8Array(atob(str).split("").map(c => c.charCodeAt(0)));
}
function toHex(u8) {
return [...u8].map(b => b.toString(16).padStart(2, "0")).join("");
}
Выбор формата влияет на безопасность и совместимость. Base64 чаще используется в веб-контексте, hex — в логах и низкоуровневых системах.
Наиболее безопасный с точки зрения браузерного окружения способ — хранение ключей исключительно в оперативной памяти:
let sessionKeyPair = nacl.box.keyPair();
Недостаток — потеря при перезагрузке страницы и отсутствие персистентности.
Хранение в localStorage выглядит простым, но создаёт
существенные риски:
localStorage.setItem("secretKey", toBase64(keyPair.secretKey));
Проблемы:
Использование допустимо только для публичных ключей или зашифрованных структур.
Более устойчивый вариант для хранения криптографических материалов:
const record = {
id: "keypair-1",
publicKey: keyPair.publicKey,
secretKey: keyPair.secretKey
};
Даже при использовании IndexedDB сохраняется проблема компрометации при XSS.
Практика “key wrapping” используется для защиты секретов:
const encryptedSecretKey = nacl.secretbox(
keyPair.secretKey,
nonce,
masterKey
);
Где:
masterKey — производный ключ от пароля (через PBKDF2 /
Argon2 вне nacl.js)nonce — уникальное случайное значениеРотация ключей — процесс регулярной замены криптографических ключей без потери совместимости с ранее зашифрованными данными.
Типовая структура хранения:
const keyRecord = {
keyId: "2026-01",
publicKey,
secretKey,
createdAt: Date.now()
};
Каждое сообщение или зашифрованный блок связывается с
keyId.
Во время переходного периода используются несколько активных ключей:
function getActiveKey(keyStore) {
return keyStore.sort((a, b) => b.createdAt - a.createdAt)[0];
}
Полное удаление ключей в JavaScript ограничено моделью управления памятью, но существуют практические подходы.
function wipe(buffer) {
buffer.fill(0);
}
Этот метод снижает вероятность восстановления данных из памяти в рамках текущего процесса.
keyPair.secretKey = null;
keyPair.publicKey = null;
Удаление ссылок помогает GC, но не гарантирует немедленного освобождения памяти.
В браузере отсутствует:
Поэтому уничтожение ключей рассматривается как снижение риска, а не абсолютное удаление.
Практическая модель уменьшения времени жизни ключа в памяти:
function encryptMessage(msg, keyPair) {
const nonce = nacl.randomBytes(24);
const encrypted = nacl.box(
msg,
nonce,
keyPair.publicKey,
keyPair.secretKey
);
keyPair.secretKey.fill(0);
return { encrypted, nonce };
}
TweetNaCl.js поддерживает сценарии, где ключи используются одноразово.
const ephemeral = nacl.box.keyPair();
Применяется для:
const sharedKey = nacl.box.before(
receiverPublicKey,
senderSecretKey
);
Полученный sharedKey может использоваться для
симметричного шифрования сообщений без повторного использования
приватных ключей.
Для структурирования управления ключами часто вводится слой абстракции:
class KeyStore {
constructor() {
this.keys = [];
}
add(keyPair) {
this.keys.push({
id: crypto.randomUUID(),
...keyPair,
createdAt: Date.now()
});
}
getLatest() {
return this.keys[this.keys.length - 1];
}
remove(id) {
this.keys = this.keys.filter(k => k.id !== id);
}
}
localStoragebox и
secretboxUint8ArrayNonce в TweetNaCl.js должен быть уникальным для каждой операции:
const nonce = nacl.randomBytes(24);
Повторное использование nonce с тем же ключом приводит к компрометации безопасности.
Модель жизненного цикла обычно включает состояния:
Каждое состояние влияет на возможность использования ключа для:
Практика уменьшения утечек через архитектурное разделение:
function cryptoScope(fn) {
const keyPair = nacl.box.keyPair();
try {
return fn(keyPair);
} finally {
keyPair.secretKey.fill(0);
}
}