Библиотека TweetNaCl.js предоставляет минималистичный набор примитивов для асимметричной и симметричной криптографии, основанный на NaCl/libsodium. Основной принцип — отсутствие «магии»: разработчик сам управляет ключами, nonce и вспомогательными значениями, что снижает скрытую сложность, но увеличивает ответственность.
Все операции выполняются синхронно, включая генерацию случайных данных. Это важно учитывать при проектировании потоков и серверной логики, особенно при высоконагруженных системах.
Для обмена зашифрованными сообщениями используется
nacl.box, основанный на Curve25519.
import nacl from 'tweetnacl';
const keyPair = nacl.box.keyPair();
console.log(keyPair.publicKey);
console.log(keyPair.secretKey);
Структура:
publicKey — может передаваться открытоsecretKey — строго конфиденциальенРазмеры:
Особенность реализации — ключи генерируются через криптографически
стойкий генератор случайных чисел, встроенный в
nacl.randomBytes.
Для цифровых подписей используется Ed25519:
const signKeyPair = nacl.sign.keyPair();
Отличие от box:
Ключевой строительный блок всей криптографии в TweetNaCl.js:
const bytes = nacl.randomBytes(32);
Используется для:
Важно:
crypto.getRandomValues в
браузереНельзя заменять на Math.random() — это ломает
криптостойкость полностью.
Nonce (number used once) — одноразовое значение, критически важное
для nacl.box и nacl.secretbox.
const nonce = nacl.randomBytes(24);
Nonce должен:
const nonce = nacl.randomBytes(24);
Плюсы:
Минусы:
const counter = new Uint32Array([messageIndex]);
const randomPart = nacl.randomBytes(20);
const nonce = new Uint8Array(24);
nonce.set(randomPart);
nonce.set(new Uint8Array(counter.buffer), 20);
Плюсы:
// пример: хеш от ключа + индекса
Используется только при строгом контроле протокола.
Самая критическая ошибка в nacl.secretbox:
const nonce = new Uint8Array(24); // ❌ всегда нули
Это полностью ломает безопасность.
TweetNaCl.js не предоставляет встроенного KDF (key derivation function), поэтому соль используется только при внешних алгоритмах:
const salt = nacl.randomBytes(16);
Типичные размеры:
Пример через Web Crypto API:
const salt = crypto.getRandomValues(new Uint8Array(16));
const keyMaterial = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode("password"),
{ name: "PBKDF2" },
false,
["deriveKey"]
);
const key = await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256"
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
В системах на TweetNaCl.js часто ключи хранятся и передаются явно:
const secretKey = nacl.randomBytes(32);
const publicKey = nacl.box.keyPair.fromSecretKey(secretKey).publicKey;
Или наоборот:
const keyPair = nacl.box.keyPair();
const extractedPublic = keyPair.publicKey;
Ключи и nonce обычно сериализуются:
const base64Key = Buffer.from(keyPair.secretKey).toString('base64');
const secretKey = Uint8Array.from(Buffer.from(base64Key, 'base64'));
nacl.box.keyPair() или
nacl.sign.keyPair()Math.random() вместо
nacl.randomBytesimport nacl from 'tweetnacl';
const keyPair = nacl.box.keyPair();
function createNonce() {
return nacl.randomBytes(24);
}
function encrypt(message, recipientPublicKey, senderSecretKey) {
const nonce = createNonce();
const encrypted = nacl.box(
new TextEncoder().encode(message),
nonce,
recipientPublicKey,
senderSecretKey
);
return { nonce, encrypted };
}