В современных веб-приложениях передача данных на сервер без защиты приводит к тому, что любая информация может быть перехвачена на уровне сети, прокси или вредоносного расширения браузера. Даже HTTPS не всегда решает задачу локального шифрования перед отправкой, особенно когда требуется защита данных «от клиента до сервера» с контролем ключей на стороне приложения.
TweetNaCl.js реализует криптографические примитивы из NaCl (Networking and Cryptography library), предоставляя минималистичный и безопасный набор функций. Основной акцент делается на простоту API и невозможность неправильного использования без явных ошибок в архитектуре.
В библиотеке используются два ключевых подхода:
Симметричное шифрование (secretbox) Один ключ используется и для шифрования, и для расшифровки. Подходит для клиент-серверного обмена, если ключ уже предварительно согласован.
Асимметричное шифрование (box) Используются две пары ключей: публичный и приватный. Данные шифруются публичным ключом получателя и расшифровываются его приватным ключом.
В контексте отправки данных на сервер чаще используется
secretbox, так как он проще и быстрее, но box
обеспечивает более гибкую модель безопасности.
npm install tweetnacl tweetnacl-util
import nacl from "tweetnacl";
import util from "tweetnacl-util";
tweetnacl-util используется для преобразования строк в
Uint8Array и обратно, поскольку криптографические функции работают
только с бинарными данными.
Для симметричного шифрования необходим 256-битный ключ:
const key = nacl.randomBytes(32);
Важно учитывать, что ключ должен быть:
На практике часто используется производный ключ (например, через PBKDF2 или Argon2 на сервере).
TweetNaCl работает только с бинарными данными, поэтому текст необходимо преобразовать:
const message = "Данные пользователя";
const messageUint8 = util.decodeUTF8(message);
Nonce (одноразовый вектор) критически важен. Он должен быть уникальным для каждой операции шифрования с одним и тем же ключом.
const nonce = nacl.randomBytes(24);
Ошибка повторного использования nonce с тем же ключом полностью компрометирует безопасность secretbox.
const encrypted = nacl.secretbox(messageUint8, nonce, key);
Результат — Uint8Array с зашифрованным содержимым.
Так как HTTP передаёт текстовые данные, бинарный результат нужно закодировать:
const encryptedBase64 = util.encodeBase64(encrypted);
const nonceBase64 = util.encodeBase64(nonce);
const payload = {
data: encryptedBase64,
nonce: nonceBase64
};
await fetch("/api/secure-endpoint", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
import nacl from "tweetnacl";
import util from "tweetnacl-util";
const key = ... // общий симметричный ключ
function decrypt(dataBase64, nonceBase64) {
const data = util.decodeBase64(dataBase64);
const nonce = util.decodeBase64(nonceBase64);
const decrypted = nacl.secretbox.open(data, nonce, key);
if (!decrypted) {
throw new Error("Ошибка расшифровки");
}
return util.encodeUTF8(decrypted);
}
Для сценариев, где сервер имеет пару ключей:
const serverKeyPair = nacl.box.keyPair();
Клиент шифрует данные публичным ключом сервера:
const nonce = nacl.randomBytes(24);
const sharedMessage = util.decodeUTF8("секретные данные");
const encrypted = nacl.box(
sharedMessage,
nonce,
serverPublicKey,
clientSecretKey
);
На сервере:
const decrypted = nacl.box.open(
encrypted,
nonce,
clientPublicKey,
serverSecretKey
);
Криптография в браузере всегда ограничена моделью доверия к клиенту. Основные проблемы возникают не в алгоритмах, а в хранении ключей:
Практическая модель:
Повтор nonce с одним ключом Самая критическая ошибка. Даже одно повторение делает возможным восстановление исходного текста.
Использование строк вместо Uint8Array Прямой вызов криптофункций со строками приводит к некорректным результатам.
Отсутствие проверки целостности данных secretbox уже включает проверку, но при неправильной обработке результата возможны логические уязвимости.
Хранение ключа в клиентском коде без защиты Любой пользователь может извлечь ключ через DevTools.
import nacl from "tweetnacl";
import util from "tweetnacl-util";
const key = nacl.randomBytes(32);
function encryptMessage(text) {
const nonce = nacl.randomBytes(24);
const messageUint8 = util.decodeUTF8(text);
const encrypted = nacl.secretbox(messageUint8, nonce, key);
return {
data: util.encodeBase64(encrypted),
nonce: util.encodeBase64(nonce)
};
}
function decryptMessage(payload) {
const data = util.decodeBase64(payload.data);
const nonce = util.decodeBase64(payload.nonce);
const decrypted = nacl.secretbox.open(data, nonce, key);
return util.encodeUTF8(decrypted);
}
Защита локальных данных перед API
Клиентское шифрование end-to-end
Подпись и проверка сообщений
nacl.signTweetNaCl.js не решает задачи:
Криптография здесь работает как слой поверх транспортного канала, а не как замена архитектурной безопасности.