nacl.util.encodeUTF8 и nacl.util.decodeUTF8

Любая криптографическая библиотека в JavaScript работает не со строками напрямую, а с байтовыми массивами. Причина проста: криптография оперирует последовательностями байтов фиксированного формата, тогда как строки в JavaScript — это абстракция над Unicode-символами.

В TweetNaCl.js (и совместимых реализациях nacl.js) для преобразования между строками и байтами используется модуль nacl.util, в котором ключевую роль играют две функции:

  • nacl.util.decodeUTF8
  • nacl.util.encodeUTF8

Именно они обеспечивают корректное преобразование данных между человеческим текстом и криптографическим представлением.


UTF-8 как базовый формат представления текста

UTF-8 — это переменной длины кодировка Unicode, где каждый символ может занимать от 1 до 4 байт. Она выбрана не случайно:

  • совместима с ASCII (первые 128 символов совпадают)
  • устойчива к платформенным различиям
  • стандарт де-факто в сетевых протоколах и криптографии

В контексте TweetNaCl.js UTF-8 используется как универсальный мост между строками JavaScript и бинарными массивами Uint8Array.


nacl.util.decodeUTF8

Функция преобразует строку JavaScript в массив байтов Uint8Array.

Сигнатура

nacl.util.decodeUTF8(string) -> Uint8Array

Поведение

  • принимает строку в формате Unicode
  • кодирует её в UTF-8
  • возвращает массив байтов

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

const message = "Hello, мир";

const bytes = nacl.util.decodeUTF8(message);

console.log(bytes);
// Uint8Array([...байты UTF-8...])

Важность в криптографии

Большинство криптографических функций TweetNaCl.js (например, nacl.box, nacl.secretbox, nacl.sign) принимают только Uint8Array.

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

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

Пример:

const msg = nacl.util.decodeUTF8("secret message");

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

Особенности кодирования UTF-8

При использовании decodeUTF8 важно учитывать:

  • символы кириллицы занимают 2 байта
  • эмодзи и редкие символы — до 4 байт
  • длина массива байтов ≠ длина строки

Пример:

"а".length === 1
nacl.util.decodeUTF8("а").length === 2

Это критически важно при работе с криптографическими буферами, где размер данных имеет значение.


nacl.util.encodeUTF8

Функция выполняет обратное преобразование: из Uint8Array в строку JavaScript.

Сигнатура

nacl.util.encodeUTF8(Uint8Array) -> string

Поведение

  • принимает массив байтов UTF-8
  • декодирует его в строку
  • возвращает корректный Unicode-стринг

Пример

const bytes = new Uint8Array([72, 101, 108, 108, 111]);

const text = nacl.util.encodeUTF8(bytes);

console.log(text); // "Hello"

Использование после расшифрования

Частый сценарий — получение расшифрованных данных:

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

if (decrypted) {
    const message = nacl.util.encodeUTF8(decrypted);
    console.log(message);
}

Здесь:

  • decrypted — бинарный массив
  • encodeUTF8 восстанавливает человекочитаемый текст

Ошибки и ограничения

Некорректные байты

Если в encodeUTF8 передать массив, не являющийся валидным UTF-8, результат может быть:

  • строка с заменяющими символами
  • исключение (в некоторых окружениях)

Потеря данных

UTF-8 строго структурирован. Если данные не были изначально текстом (например, случайный бинарный массив), попытка декодирования:

nacl.util.encodeUTF8(randomBytes)

не гарантирует осмысленного результата.


Разница между текстом и бинарными данными

Ключевая ошибка при работе с TweetNaCl.js — смешивание типов данных.

Неправильно

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

Правильно

const msg = nacl.util.decodeUTF8("hello");
nacl.secretbox(msg, nonce, key);

Криптографические функции работают только с байтами.


Альтернатива через TextEncoder / TextDecoder

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

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

const decoder = new TextDecoder();
const text = decoder.decode(bytes);

Однако nacl.util сохраняется как:

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

Влияние на безопасность и корректность данных

Корректное использование UTF-8 преобразований напрямую влияет на:

  • воспроизводимость шифрования
  • совпадение хэшей и подписей
  • совместимость между платформами (Node.js, браузер, мобильные среды)

Даже одно неверное преобразование строки в байты приводит к:

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

Практический шаблон работы с текстом

// кодирование строки в байты
const data = nacl.util.decodeUTF8("confidential");

// криптографическая операция
const encrypted = nacl.secretbox(data, nonce, key);

// обратное преобразование
const decrypted = nacl.secretbox.open(encrypted, nonce, key);

const text = decrypted ? nacl.util.encodeUTF8(decrypted) : null;

Работа с многоязычным текстом

UTF-8 гарантирует поддержку любых языков:

const text = "Привет ? こんにちは";

const bytes = nacl.util.decodeUTF8(text);
const restored = nacl.util.encodeUTF8(bytes);

Строка сохраняется без потерь при корректном round-trip преобразовании.


Поведение с пустыми строками и массивами

nacl.util.decodeUTF8("") // Uint8Array([])
nacl.util.encodeUTF8(new Uint8Array([])) // ""

Это важно при обработке сообщений нулевой длины или пустых payload в протоколах.


Типичные ошибки интеграции

  • передача строк вместо Uint8Array
  • повторное декодирование уже декодированных байтов
  • попытка кодировать случайные бинарные данные как UTF-8
  • игнорирование разницы длины строки и байтового массива

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