Клиент-серверная архитектура с нулевым знанием сервера

Архитектуры, в которых сервер не обладает возможностью расшифровывать пользовательские данные, строятся на принципе end-to-end encryption. В контексте JavaScript-библиотек TweetNaCl.js и nacl.js это означает, что криптографические операции выполняются исключительно на стороне клиента, а сервер выступает лишь как транспорт и хранилище зашифрованных сообщений.

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


Криптографическая основа NaCl и TweetNaCl.js

TweetNaCl.js представляет собой компактную реализацию библиотеки NaCl (Networking and Cryptography library), адаптированную для JavaScript. Она реализует строго ограниченный набор проверенных примитивов:

  • public-key authenticated encryption (box)
  • secret-key encryption (secretbox)
  • public-key signatures (sign)
  • scalar multiplication (Curve25519)
  • hashing (SHA-512, HSalsa20)

В контексте клиент-серверной архитектуры наибольшую роль играет конструкция box, основанная на Curve25519 и XSalsa20-Poly1305.


Принцип «сервер ничего не знает»

Модель строится вокруг следующего разделения:

  • Клиент A: генерирует ключи и шифрует данные
  • Клиент B: расшифровывает данные
  • Сервер: хранит и пересылает только ciphertext

Сервер не обладает:

  • приватными ключами пользователей
  • сессионными ключами
  • возможностью выполнить дешифрование

Это делает сервер криптографически «слепым» к содержимому сообщений.


Генерация ключей на клиенте

Каждый участник системы создаёт собственную пару ключей:

import nacl from 'tweetnacl';
import util from 'tweetnacl-util';

const keyPair = nacl.box.keyPair();

const publicKey = keyPair.publicKey;
const secretKey = keyPair.secretKey;
  • publicKey распространяется через сервер
  • secretKey никогда не покидает устройство клиента

В модели nacl.js / TweetNaCl.js приватный ключ считается полностью недоступным для инфраструктуры.


Обмен публичными ключами через сервер

Сервер выполняет роль каталога:

Client A → сервер: publicKeyA
Client B → сервер: publicKeyB

Сервер хранит:

  • идентификатор пользователя
  • публичный ключ
  • метаданные (опционально)

Сервер не может использовать эти данные для расшифровки сообщений.


Формирование защищённого канала (Curve25519)

Для шифрования сообщений используется nacl.box, который требует:

  • публичный ключ получателя
  • приватный ключ отправителя
  • nonce (одноразовый вектор)
const nonce = nacl.randomBytes(nacl.box.nonceLength);

const message = util.decodeUTF8('секретное сообщение');

const encrypted = nacl.box(
  message,
  nonce,
  recipientPublicKey,
  senderSecretKey
);

Дешифрование на стороне получателя

const decrypted = nacl.box.open(
  encrypted,
  nonce,
  senderPublicKey,
  recipientSecretKey
);

const text = util.encodeUTF8(decrypted);

Ключевой момент: сервер никогда не участвует в этом процессе.


Архитектура передачи сообщений

Типичный поток данных выглядит следующим образом:

  1. Клиент A шифрует сообщение

  2. Отправляет на сервер:

    • ciphertext
    • nonce
    • идентификатор получателя
  3. Сервер сохраняет/пересылает пакет

  4. Клиент B получает пакет

  5. Расшифровывает локально

Структура сообщения на сервере:

{
  "from": "A",
  "to": "B",
  "nonce": "...",
  "ciphertext": "..."
}

Роль nonce и уникальности шифрования

Nonce в XSalsa20-Poly1305 должен быть:

  • уникальным для каждой операции
  • не повторяться для одного ключа
const nonce = nacl.randomBytes(24);

Повтор nonce с тем же ключом приводит к криптографической компрометации.


Модель угроз и её границы

При использовании TweetNaCl.js сервер считается полностью недоверенным. Это означает:

Сервер может:

  • читать метаданные (время, размер сообщений)
  • пересылать сообщения
  • удалять сообщения
  • хранить зашифрованные данные

Сервер не может:

  • расшифровать сообщения
  • подменить содержимое без обнаружения
  • восстановить приватные ключи

Аутентифицированное шифрование box

nacl.box одновременно обеспечивает:

  • конфиденциальность (confidentiality)
  • целостность (integrity)
  • аутентификацию отправителя

Это достигается через Poly1305 MAC поверх XSalsa20.


Сессионные ключи и оптимизация

Для уменьшения вычислительной нагрузки часто применяются сессионные ключи.

Идея:

  1. Клиенты выполняют box.before
  2. Получают общий симметричный ключ
  3. Используют box.after для сообщений
const sharedKey = nacl.box.before(
  recipientPublicKey,
  senderSecretKey
);

const encrypted = nacl.box.after(
  message,
  nonce,
  sharedKey
);

Это снижает стоимость операций по Curve25519.


Forward secrecy (прямая секретность)

В расширенных моделях используется:

  • генерация ephemeral key pair для каждой сессии
  • регулярная ротация ключей

Это означает:

  • компрометация одного ключа не раскрывает прошлые сообщения
  • каждое соединение имеет ограниченный криптографический радиус действия

Хранение данных на сервере

Сервер может хранить:

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

Пример схемы БД:

messages(
  id,
  sender_id,
  receiver_id,
  nonce,
  ciphertext,
  timestamp
)

Никакие поля не содержат открытого текста.


Клиентская ответственность

В такой архитектуре клиент становится доверенной криптографической средой:

  • генерация ключей
  • хранение private key (IndexedDB / secure storage)
  • шифрование перед отправкой
  • дешифрование после получения

Любая утечка приватного ключа происходит на стороне клиента, а не сервера.


Реализация end-to-end чата

Минимальный поток:

Отправка

function sendMessage(text, recipientPublicKey, senderSecretKey) {
  const nonce = nacl.randomBytes(24);
  const messageUint8 = util.decodeUTF8(text);

  const encrypted = nacl.box(
    messageUint8,
    nonce,
    recipientPublicKey,
    senderSecretKey
  );

  return {
    nonce: util.encodeBase64(nonce),
    ciphertext: util.encodeBase64(encrypted)
  };
}

Получение

function receiveMessage(payload, senderPublicKey, recipientSecretKey) {
  const nonce = util.decodeBase64(payload.nonce);
  const ciphertext = util.decodeBase64(payload.ciphertext);

  const decrypted = nacl.box.open(
    ciphertext,
    nonce,
    senderPublicKey,
    recipientSecretKey
  );

  return util.encodeUTF8(decrypted);
}

Масштабирование zero-knowledge архитектуры

При росте системы возникают дополнительные требования:

  • управление публичными ключами (key directory service)
  • синхронизация ключей между устройствами
  • мультидевайсная модель
  • восстановление аккаунта без серверного знания ключей

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


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

Несмотря на криптографическую строгость, модель имеет ограничения:

  • потеря private key означает потерю доступа к данным
  • невозможность серверного поиска по содержимому
  • сложность резервного копирования
  • зависимость безопасности от клиента (браузер, устройство)

Практическая роль TweetNaCl.js в современной архитектуре

TweetNaCl.js используется там, где требуется:

  • мессенджеры с end-to-end encryption
  • защищённые API поверх небезопасных серверов
  • распределённые системы обмена данными
  • клиент-ориентированные криптосистемы

Минимализм библиотеки снижает риск ошибок реализации, что критично для криптографии.


Разделение доверия в системе

В такой архитектуре доверие распределяется следующим образом:

  • сервер: недоверенный транспорт
  • сеть: недоверенная среда
  • клиент: единственная доверенная точка

Это полностью меняет классическую модель клиент-сервер, превращая сервер в пассивный узел хранения и маршрутизации.