nacl.secretbox: шифрование и расшифровка

Общая модель безопасности secretbox

nacl.secretbox реализует симметричное шифрование на основе алгоритма Salsa20 (потоковый шифр) и аутентификационного кода Poly1305. Комбинация этих двух примитивов формирует схему authenticated encryption (AEAD), в которой одновременно обеспечивается:

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

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


Криптографическая модель

В основе работы secretbox лежит схема XSalsa20-Poly1305:

  • XSalsa20 используется для генерации потокового ключа и шифрования данных через операцию XOR
  • Poly1305 вычисляет MAC (message authentication code) по зашифрованным данным

Таким образом, результатом является:

ciphertext + authentication tag

Любое изменение зашифрованных данных приводит к провалу проверки при расшифровке.


Ключи и их свойства

nacl.secretbox использует симметричный ключ длиной 32 байта.

const key = nacl.randomBytes(32);

Свойства ключа:

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

Ключ не должен быть производным от пароля без использования KDF (например, Argon2 или PBKDF2), так как библиотека не выполняет деривацию ключа самостоятельно.


Nonce: критически важный параметр

Nonce (number used once) — это 24-байтовое значение, которое обязано быть уникальным для каждой операции шифрования с одним и тем же ключом.

const nonce = nacl.randomBytes(24);

Свойства nonce:

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

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

Функция шифрования:

nacl.secretbox(message, nonce, key)

Параметры:

  • message — Uint8Array с исходными данными
  • nonce — 24-байтовый nonce
  • key — 32-байтовый секретный ключ

Пример:

const message = new TextEncoder().encode("секретное сообщение");
const key = nacl.randomBytes(32);
const nonce = nacl.randomBytes(24);

const boxed = nacl.secretbox(message, nonce, key);

Результат:

  • boxed содержит зашифрованный текст + MAC
  • длина результата = длина сообщения + 16 байт (Poly1305 tag)

Расшифровка данных

Функция расшифровки:

nacl.secretbox.open(boxedMessage, nonce, key)

Пример:

const opened = nacl.secretbox.open(boxed, nonce, key);

if (!opened) {
  throw new Error("Ошибка расшифровки или повреждение данных");
}

const decoded = new TextDecoder().decode(opened);

Особенности поведения:

  • при успешной проверке возвращается Uint8Array
  • при ошибке возвращается null
  • проверка MAC выполняется до раскрытия данных

Внутренний процесс работы secretbox

Процесс шифрования можно представить в виде последовательности:

  1. Генерация keystream через XSalsa20:

    stream = XSalsa20(key, nonce)
  2. XOR исходного сообщения с keystream:

    ciphertext = message XOR stream
  3. Вычисление Poly1305 MAC:

    tag = Poly1305(ciphertext)
  4. Объединение результата:

    output = ciphertext + tag

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


Безопасные практики использования

Уникальность nonce

Критическое требование:

  • nonce никогда не должен повторяться при одном и том же ключе

Практическая схема:

const nonce = nacl.randomBytes(24);

Для потоковой передачи данных используется счётчик или комбинированный nonce (например, timestamp + random suffix).


Хранение ключей

Рекомендуемые подходы:

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

Размер сообщений

secretbox не накладывает ограничений на размер данных, однако:

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

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

Повтор nonce

Самая критическая ошибка:

  • повтор nonce с тем же ключом позволяет восстановить XOR двух сообщений
  • приводит к полной компрометации канала

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

Все входные данные должны быть бинарными:

const message = new TextEncoder().encode("text");

Передача строки напрямую недопустима.


Игнорирование результата open()

const msg = nacl.secretbox.open(box, nonce, key);

Если msg === null, дальнейшая обработка недопустима.


Преобразование строк и бинарных данных

Для интеграции с текстовыми данными используются:

TextEncoder / TextDecoder

Пример полного цикла:

const encoder = new TextEncoder();
const decoder = new TextDecoder();

const message = encoder.encode("данные");
const key = nacl.randomBytes(32);
const nonce = nacl.randomBytes(24);

const encrypted = nacl.secretbox(message, nonce, key);
const decrypted = nacl.secretbox.open(encrypted, nonce, key);

const text = decoder.decode(decrypted);

Производительность и ограничения

Характеристики:

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

Ограничения:

  • отсутствие встроенной схемы управления ключами
  • нет поддержки streaming encryption
  • необходимость внешнего контроля nonce

Сравнение с другими схемами в NaCl

  • nacl.secretbox — симметричное шифрование
  • nacl.box — асимметричное шифрование (Curve25519)
  • nacl.sign — цифровая подпись

secretbox применяется в случаях, где:

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

Структура данных secretbox

Формат результата:

[ ciphertext || 16-byte Poly1305 tag ]

Nonce хранится отдельно и не входит в результат, поэтому его необходимо передавать вместе с сообщением через внешний протокол (например, JSON или бинарный контейнер).