TweetNaCl.js в React Native и мобильных окружениях

TweetNaCl.js и его JavaScript-реализации семейства nacl.js представляют собой компактные, полностью программные реализации криптографических примитивов NaCl (Networking and Cryptography library). В мобильных средах и React Native они часто становятся базовым решением для end-to-end шифрования, цифровых подписей и безопасного обмена данными без необходимости нативных модулей.


TweetNaCl.js — это порт оригинальной NaCl, написанный на чистом JavaScript с минимальной зависимостью от платформы. Это определяет его ключевые свойства:

  • отсутствие нативных зависимостей
  • работа через Uint8Array
  • предсказуемое поведение в браузере и React Native
  • компактный размер (ориентирован на минимализм)
  • ограниченный, но безопасный набор криптографических примитивов

Основные алгоритмы:

  • Curve25519 (обмен ключами)
  • Salsa20 / XSalsa20 (шифрование)
  • Poly1305 (аутентификация)
  • Ed25519 (подписи)

TweetNaCl.js vs nacl.js

Под обозначением nacl.js часто встречаются форки и обёртки над TweetNaCl:

  • TweetNaCl.js — эталонная минимальная реализация
  • nacl.js — более «дружелюбные» обёртки с утилитами кодирования
  • tweetnacl-util — вспомогательные функции (base64, utf8)

В мобильных проектах React Native чаще используется комбинация:

  • tweetnacl
  • tweetnacl-util
  • polyfill для randomBytes или crypto.getRandomValues

Особенности работы в React Native

React Native не является полноценной браузерной средой, поэтому возникают системные ограничения.

1. Отсутствие crypto.getRandomValues

TweetNaCl.js требует криптографически стойкий генератор случайных чисел.

В браузере используется:

crypto.getRandomValues(new Uint8Array(32));

В React Native этого API нет по умолчанию.

Решения:

react-native-get-random-values

import 'react-native-get-random-values';

После подключения глобально появляется совместимый crypto.


2. Buffer и Uint8Array

TweetNaCl работает только с Uint8Array. Однако мобильные приложения часто оперируют строками.

Типичная проблема:

  • данные приходят как string
  • криптография требует byte array

Используется преобразование:

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

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

3. Hermes engine

При использовании Hermes:

  • ускоряется работа с массивами
  • но иногда ломаются старые polyfill-решения Buffer

Рекомендуется избегать Buffer и использовать Uint8Array напрямую.


Базовые операции в мобильной среде

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

TweetNaCl использует публично-асимметричные ключи Curve25519:

import nacl from 'tweetnacl';

const keyPair = nacl.box.keyPair();

const publicKey = keyPair.publicKey;
const secretKey = keyPair.secretKey;

В React Native важно хранить secretKey только в защищённых хранилищах:

  • SecureStore (Expo)
  • Keychain (iOS)
  • Keystore (Android)

Шифрование сообщений (Box)

Модель box используется для публичного шифрования:

const nonce = nacl.randomBytes(24);

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

Особенности мобильных приложений:

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

Дешифрование

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

Если данные повреждены или ключи не совпадают — возвращается null.


SecretBox для локального шифрования

Для хранения данных на устройстве используется symmetric encryption:

const key = nacl.randomBytes(32);
const nonce = nacl.randomBytes(24);

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

Применение в мобильных приложениях:

  • локальное шифрование кеша
  • защита offline-данных
  • шифрование токенов перед записью

Подписи (nacl.sign)

Подписи применяются для проверки целостности сообщений:

const keyPair = nacl.sign.keyPair();

const signed = nacl.sign(messageUint8, keyPair.secretKey);

const verified = nacl.sign.open(signed, keyPair.publicKey);

В мобильных API часто используется:

  • проверка обновлений данных
  • защита API-запросов
  • подтверждение транзакций

Проблемы кодирования в мобильных приложениях

UTF-8 vs Base64

TweetNaCl не работает со строками напрямую.

Частые преобразования:

const encoded = util.encodeBase64(encrypted);
const decoded = util.decodeBase64(encoded);

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

  • разные источники данных используют разные кодировки
  • JSON API часто требует base64
  • WebSocket может использовать ArrayBuffer

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

TweetNaCl.js написан без нативных ускорений, поэтому:

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

Рекомендации:

  • кешировать keyPair
  • использовать memoization для nonce-логики
  • не выполнять массовое шифрование в render-цикле React

Особенности Android и iOS

Android

  • JavaScriptCore или Hermes
  • различия в random polyfill могут влиять на криптостойкость
  • важно проверять корректность getRandomValues

iOS

  • более стабильное выполнение Uint8Array операций
  • меньше проблем с crypto API
  • возможны ограничения при background execution

Secure storage интеграция

TweetNaCl не решает задачу хранения ключей.

Типовая схема:

  1. генерация keyPair через nacl
  2. сохранение secretKey в SecureStore/Keychain
  3. загрузка publicKey на сервер
  4. восстановление ключа при запуске приложения

Ошибки и типичные проблемы

1. “Invalid nonce size”

Nonce для box всегда 24 байта:

nacl.randomBytes(24)

2. “TypeError: crypto.getRandomValues is not a function”

Решается установкой polyfill:

import 'react-native-get-random-values';

3. null при расшифровке

Причины:

  • неверный publicKey
  • повреждённый ciphertext
  • повторное использование nonce
  • несоответствие encoding

Оптимизация использования в мобильных приложениях

Ключевые практики:

  • хранение ключей в памяти только при необходимости
  • уничтожение sensitive buffers после использования
  • минимизация копирования Uint8Array
  • избегание JSON сериализации бинарных данных без base64

Альтернативы и границы применения

TweetNaCl.js подходит для:

  • end-to-end encryption
  • secure messaging
  • подписи запросов
  • локального шифрования данных

Не подходит для:

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

Использование в Expo и managed workflow

В Expo требуется:

  • expo-crypto или react-native-get-random-values
  • отсутствие нативных модулей позволяет использовать TweetNaCl без eject

Пример инициализации:

import 'react-native-get-random-values';
import nacl from 'tweetnacl';

Типовая архитектура мобильного криптослоя

  • UI слой (React Native)
  • Crypto service (TweetNaCl.js wrapper)
  • Storage layer (Keychain / SecureStore)
  • Network layer (API / WebSocket)

Crypto service обычно инкапсулирует:

  • генерацию ключей
  • шифрование/дешифрование
  • кодирование base64
  • управление nonce

Практическая структура обёртки

class CryptoService {
  constructor(nacl, util) {
    this.nacl = nacl;
    this.util = util;
  }

  generateKeyPair() {
    return this.nacl.box.keyPair();
  }

  encrypt(message, publicKey, secretKey) {
    const nonce = this.nacl.randomBytes(24);
    const messageUint8 = this.util.decodeUTF8(message);

    const box = this.nacl.box(messageUint8, nonce, publicKey, secretKey);

    return {
      nonce: this.util.encodeBase64(nonce),
      box: this.util.encodeBase64(box),
    };
  }

  decrypt(box, nonce, publicKey, secretKey) {
    return this.nacl.box.open(
      this.util.decodeBase64(box),
      this.util.decodeBase64(nonce),
      publicKey,
      secretKey
    );
  }
}

Ключевые ограничения мобильного использования

  • отсутствие аппаратного ускорения
  • зависимость от JS runtime
  • чувствительность к неправильным polyfill
  • необходимость строгого контроля типов данных (Uint8Array vs string)

Работа TweetNaCl.js в React Native строится вокруг строгого соблюдения бинарных типов, корректного управления случайностью и грамотной интеграции с мобильными хранилищами ключей.