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

Неверное обращение с ключевым материалом — одна из самых частых проблем при использовании TweetNaCl.js / nacl.js. Библиотека построена вокруг строгих требований к типам и размерам ключей:

  • secretbox требует ключ длиной 32 байта
  • box использует пару ключей (public/secret), каждый строго фиксированного размера
  • любые отклонения приводят либо к исключениям, либо к криптографически небезопасным результатам

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

const key = "mysecretkey";
nacl.secretbox(msg, nonce, key); // некорректно

Ключ всегда должен быть представлен как Uint8Array. Строка не интерпретируется автоматически как байты.


Использование неправильных кодировок данных

TweetNaCl.js работает исключительно с бинарными данными. Любая строка должна быть явно преобразована.

Частая ошибка — передача текста напрямую:

nacl.secretbox("hello world", nonce, key);

Правильный подход:

const encoder = new TextEncoder();
const message = encoder.encode("hello world");

При расшифровке требуется обратное преобразование через TextDecoder.

Нарушение этого правила приводит к:

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

Повторное использование nonce

Nonce (одноразовый вектор) — критический элемент безопасности. В TweetNaCl.js он должен быть:

  • уникальным для каждой операции шифрования
  • длиной 24 байта

Самая опасная ошибка — повторное использование nonce с тем же ключом:

const nonce = nacl.randomBytes(24);

const c1 = nacl.secretbox(m1, nonce, key);
const c2 = nacl.secretbox(m2, nonce, key); // критическая уязвимость

Повтор nonce приводит к утечке информации о сообщениях через криптоанализ.

Недопустимо также:

  • генерация nonce через Math.random()
  • использование фиксированного массива
  • инкремент без контроля уникальности в распределённых системах

Использование Math.random вместо криптографического генератора

TweetNaCl.js предоставляет nacl.randomBytes, который использует криптографически стойкий источник случайности.

Ошибочный подход:

const nonce = new Uint8Array(24).map(() => Math.random() * 256);

Такой nonce предсказуем и ломает безопасность всей схемы.

Корректный вариант:

const nonce = nacl.randomBytes(24);

Путаница между secretbox и box

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

  • nacl.secretbox — симметричное шифрование
  • nacl.box — асимметричное шифрование (публичный/приватный ключ)

Типичная ошибка — попытка использовать public key в secretbox:

nacl.secretbox(msg, nonce, publicKey); // логически неверно

Или наоборот — попытка заменить ключевую пару одним секретом в box.

Правильное понимание:

  • secretbox — один общий секрет
  • box — обмен ключами через Diffie-Hellman

Игнорирование длины выходных данных

Расшифровка может возвращать null, если данные повреждены или ключ неверный.

Частая ошибка — отсутствие проверки результата:

const decrypted = nacl.secretbox.open(cipher, nonce, key);
// использование decrypted без проверки

Корректный подход:

if (!decrypted) {
  throw new Error("decryption failed");
}

Игнорирование этого поведения приводит к обработке null как данных и последующим логическим сбоям.


Неправильное хранение и передача ключей

Распространённые ошибки:

  • хранение ключей в localStorage без защиты
  • передача ключей через URL-параметры
  • сериализация ключей через JSON.stringify без кодирования

Пример проблемного хранения:

localStorage.setItem("key", key); // превращается в строку "[object Uint8Array]"

Правильный способ — явное кодирование:

const b64 = btoa(String.fromCharCode(...key));

И обратное восстановление через декодирование в Uint8Array.


Ошибки работы с box (асимметричное шифрование)

При использовании nacl.box часто нарушается схема обмена ключами:

  • использование одного и того же nonce для разных сообщений
  • повторное использование ephemeral key pair
  • неправильная передача public key (обрезание массива, строковое преобразование)

Особенно критично:

const shared = nacl.box.before(publicKey, secretKey);

Любое несоответствие ключей делает результат либо null, либо уязвимым к атаке повторного воспроизведения.


Неправильная сериализация зашифрованных данных

TweetNaCl.js возвращает бинарный массив, который нельзя напрямую передавать как JSON.

Ошибка:

JSON.stringify(ciphertext);

Результат — потеря данных или некорректное восстановление.

Корректный подход:

  • Base64
  • Hex encoding
  • Uint8Array через ArrayBuffer

Потеря данных при преобразованиях типов

Особенно часто проявляется в браузерных приложениях:

  • смешивание Buffer (Node.js) и Uint8Array
  • неявное приведение типов
  • использование TypedArray как обычного массива

Пример ошибки:

const arr = Buffer.from(cipher);
nacl.secretbox.open(arr, nonce, key);

Buffer может работать, но несовместимость возникает при дальнейших операциях.


Неправильное понимание детерминированности

Криптосистемы TweetNaCl.js не являются детерминированными из-за nonce.

Ошибка ожидания одинакового ciphertext:

secretbox(msg, nonce, key) === secretbox(msg, nonce, key)

При изменении nonce результат всегда другой, и это нормальное поведение.


Отсутствие контроля целостности данных

Хотя secretbox уже включает Poly1305 MAC, распространённая ошибка — самостоятельная обрезка или модификация ciphertext:

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

Любое изменение делает расшифровку невозможной или возвращает null.


Смешивание библиотек nacl.js и TweetNaCl.js

Старые реализации nacl.js и современный tweetnacl.js не всегда совместимы на уровне форматов:

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

Типичная ошибка — использование ключей из одной библиотеки в другой без преобразования.


Игнорирование требований к размеру входных данных

TweetNaCl.js не валидирует содержимое глубоко, но требует точных размеров:

  • nonce: 24 байта
  • key: 32 байта
  • publicKey: 32 байта
  • signature: 64 байта

Любое отклонение приводит к тихим сбоям или null без пояснений.


Логические ошибки при обработке ошибок криптографии

Вместо явной обработки часто используется:

const result = nacl.secretbox.open(...);
console.log(result);

При null система продолжает работу с повреждёнными данными, что создаёт цепные ошибки на уровне бизнес-логики.

Правильный подход — строгая проверка результата на каждом этапе криптоопераций.