nacl.box.open: расшифровка и верификация

Функция nacl.box.open предназначена для расшифровки сообщения и одновременной проверки его подлинности. Она является обратной операцией к nacl.box и используется в асимметричной криптографии на основе кривой Curve25519 и алгоритма XSalsa20-Poly1305.

Ключевая особенность: расшифровка выполняется только если сообщение прошло аутентификацию. В противном случае функция возвращает null.


Сигнатура функции

nacl.box.open(box, nonce, publicKey, secretKey)

Параметры:

  • box — зашифрованное сообщение (Uint8Array)
  • nonce — одноразовый номер (Uint8Array длиной 24 байта)
  • publicKey — публичный ключ отправителя (Uint8Array длиной 32 байта)
  • secretKey — приватный ключ получателя (Uint8Array длиной 32 байта)

Возвращаемое значение:

  • Uint8Array с расшифрованными данными при успехе
  • null при ошибке аутентификации или повреждении данных

Принцип работы

Процесс nacl.box.open включает два этапа:

  1. Вычисление общего секрета (shared key) Используется алгоритм Диффи–Хеллмана на Curve25519:

    sharedKey = scalarMult(secretKey_receiver, publicKey_sender)
  2. Расшифровка и проверка MAC С помощью XSalsa20 выполняется расшифровка, а Poly1305 проверяет целостность и подлинность.

Если MAC (Message Authentication Code) не совпадает — данные считаются поддельными.


Гарантии безопасности

nacl.box.open обеспечивает:

  • Конфиденциальность — данные доступны только владельцу приватного ключа
  • Аутентичность — подтверждение, что сообщение пришло от владельца публичного ключа
  • Целостность — защита от изменения данных

Важно: функция не различает тип ошибки — это сделано намеренно для предотвращения атак по побочным каналам.


Пример использования

const nacl = require('tweetnacl');

// Генерация ключей
const alice = nacl.box.keyPair();
const bob = nacl.box.keyPair();

// Сообщение
const message = new TextEncoder().encode("Secret message");

// nonce
const nonce = nacl.randomBytes(nacl.box.nonceLength);

// Шифрование (Алиса → Боб)
const encrypted = nacl.box(
  message,
  nonce,
  bob.publicKey,
  alice.secretKey
);

// Расшифровка (Боб)
const decrypted = nacl.box.open(
  encrypted,
  nonce,
  alice.publicKey,
  bob.secretKey
);

if (decrypted) {
  console.log(new TextDecoder().decode(decrypted));
} else {
  console.log("Ошибка аутентификации");
}

Поведение при ошибках

Если:

  • ключи не соответствуют друг другу
  • nonce изменён
  • данные повреждены
  • сообщение подделано

то результат будет:

null

Это критически важно: никаких исключений не выбрасывается — только null.


Роль nonce

nonce (number used once) — обязательный параметр, который:

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

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


Важные ограничения

  • Нельзя использовать nacl.box.open без проверки результата
  • Нельзя игнорировать null
  • Нельзя повторно использовать nonce с одинаковой парой ключей

Отличие от nacl.secretbox.open

Характеристика nacl.box.open nacl.secretbox.open
Тип криптографии Асимметричная Симметричная
Ключи Пара ключей Один общий ключ
Аутентификация Да Да
Использование Обмен между сторонами Локальное шифрование

Использование с precomputed ключами

Для повышения производительности при множественных сообщениях:

const sharedKey = nacl.box.before(theirPublicKey, mySecretKey);

const decrypted = nacl.box.open.after(
  box,
  nonce,
  sharedKey
);

Преимущества:

  • ускорение за счёт повторного использования shared key
  • уменьшение вычислительной нагрузки

Формат данных

  • Все входные и выходные данные — Uint8Array
  • Для работы со строками требуется кодирование:
const encoder = new TextEncoder();
const decoder = new TextDecoder();

Типичные ошибки

1. Перепутаны ключи

// Неверно
nacl.box.open(box, nonce, myPublicKey, theirSecretKey);

Ключи должны быть:

  • публичный ключ отправителя
  • приватный ключ получателя

2. Использование строки вместо Uint8Array

// Неверно
nacl.box.open("encrypted", nonce, pub, sec);

3. Игнорирование результата

const result = nacl.box.open(...);
// Нет проверки на null — потенциальная уязвимость

Проверка целостности

nacl.box.open выполняет строгую проверку MAC. Даже изменение одного бита приведёт к отказу:

encrypted[0] ^= 1;

const result = nacl.box.open(...);
// result === null

Внутренняя структура box

Зашифрованное сообщение включает:

  • Poly1305 MAC (16 байт)
  • Зашифрованные данные

Именно MAC проверяется перед расшифровкой.


Безопасная практика

  • Генерировать nonce через nacl.randomBytes
  • Хранить ключи в защищённой памяти
  • Проверять результат на null
  • Не использовать повторно nonce
  • Использовать box.before при большом объёме сообщений

Связь с криптографическими примитивами

nacl.box.open объединяет:

  • Curve25519 — обмен ключами
  • XSalsa20 — потоковое шифрование
  • Poly1305 — аутентификация

Эта комбинация известна как crypto_box в библиотеке NaCl.


Минимальный жизненный цикл сообщения

  1. Генерация nonce
  2. Шифрование через nacl.box
  3. Передача: box + nonce + publicKey
  4. Расшифровка через nacl.box.open
  5. Проверка результата

Особенности реализации TweetNaCl.js

  • Полностью написана на JavaScript
  • Не использует WebCrypto API
  • Работает одинаково в Node.js и браузере
  • Не имеет зависимостей

Производительность

  • Основная нагрузка — на scalar multiplication (Curve25519)
  • Использование before/after снижает накладные расходы
  • Подходит для реального времени при умеренных нагрузках

Проверка подлинности отправителя

Так как используется публичный ключ отправителя, получатель может быть уверен:

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

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

  • Нет встроенной защиты от повторной передачи (replay attack)
  • Нет встроенной идентификации пользователей
  • Нет механизма хранения ключей

Эти задачи решаются на уровне протокола.


Совместимость

nacl.box.open совместим с:

  • оригинальной библиотекой NaCl
  • libsodium
  • другими реализациями crypto_box

при условии:

  • одинаковых алгоритмов
  • корректного формата данных

Резюме поведения

  • успешная расшифровка → Uint8Array
  • ошибка → null
  • исключений нет
  • безопасность встроена на уровне API