Шифрование данных перед отправкой на сервер

В современных веб-приложениях передача данных на сервер без защиты приводит к тому, что любая информация может быть перехвачена на уровне сети, прокси или вредоносного расширения браузера. Даже 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

Nonce (одноразовый вектор) критически важен. Он должен быть уникальным для каждой операции шифрования с одним и тем же ключом.

const nonce = nacl.randomBytes(24);

Ошибка повторного использования nonce с тем же ключом полностью компрометирует безопасность secretbox.


Шифрование данных (secretbox)

const encrypted = nacl.secretbox(messageUint8, nonce, key);

Результат — Uint8Array с зашифрованным содержимым.


Кодирование для передачи на сервер

Так как HTTP передаёт текстовые данные, бинарный результат нужно закодировать:

const encryptedBase64 = util.encodeBase64(encrypted);
const nonceBase64 = util.encodeBase64(nonce);

Формирование payload для отправки

const payload = {
  data: encryptedBase64,
  nonce: nonceBase64
};

Отправка на сервер

await fetch("/api/secure-endpoint", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(payload)
});

Расшифровка на сервере (Node.js)

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);
}

Использование асимметричного шифрования (box)

Для сценариев, где сервер имеет пару ключей:

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
);

Важность управления ключами

Криптография в браузере всегда ограничена моделью доверия к клиенту. Основные проблемы возникают не в алгоритмах, а в хранении ключей:

  • хранение ключей в localStorage делает их доступными для XSS
  • хранение в памяти ограничено временем жизни страницы
  • передача ключей через JS-код не защищена от анализа

Практическая модель:

  • ключи генерируются на сервере
  • клиент получает временный ключ с ограниченным сроком жизни
  • используется HTTPS как транспортный уровень защиты

Типичные ошибки при использовании TweetNaCl.js

Повтор nonce с одним ключом Самая критическая ошибка. Даже одно повторение делает возможным восстановление исходного текста.

Использование строк вместо Uint8Array Прямой вызов криптофункций со строками приводит к некорректным результатам.

Отсутствие проверки целостности данных secretbox уже включает проверку, но при неправильной обработке результата возможны логические уязвимости.

Хранение ключа в клиентском коде без защиты Любой пользователь может извлечь ключ через DevTools.


Минимальный рабочий пример end-to-end

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

  • шифрование формы до отправки
  • защита от MITM внутри корпоративных сетей

Клиентское шифрование end-to-end

  • сервер хранит только зашифрованные данные
  • расшифровка выполняется на доверенной стороне

Подпись и проверка сообщений

  • дополнительно к шифрованию используются подписи через nacl.sign

Ограничения модели

TweetNaCl.js не решает задачи:

  • управления пользователями и авторизацией
  • защиты от компрометации клиента
  • скрытия данных от самого клиента
  • защиты при наличии XSS уязвимостей

Криптография здесь работает как слой поверх транспортного канала, а не как замена архитектурной безопасности.