При работе с конфиденциальными данными в JavaScript-приложениях одной из ключевых задач становится защита информации до момента её попадания в хранилище. Особенно это актуально для архитектур, где клиентская часть взаимодействует с удалённой базой данных напрямую или через минимальный серверный слой. В таких сценариях библиотека TweetNaCl.js (или её эквивалент nacl.js) предоставляет компактный и надёжный криптографический инструментарий, основанный на проверенных примитивах NaCl (Networking and Cryptography library).
TweetNaCl.js реализует упрощённую версию NaCl с акцентом на минимализм и безопасность. В контексте шифрования данных перед записью в базу данных ключевыми являются следующие функции:
nacl.box — асимметричное шифрование с использованием
публичного и приватного ключейnacl.secretbox — симметричное шифрование с секретным
ключомnacl.randomBytes — генерация криптографически стойких
случайных значенийnacl.box.keyPair — генерация пары ключей
(public/private)На практике именно комбинация box и корректной работы с
nonce позволяет реализовать устойчивую схему защиты данных перед
сохранением.
Перед записью в базу данных данные должны быть преобразованы в зашифрованный формат. Общая схема выглядит следующим образом:
Ключевой момент: база данных никогда не получает исходный текст в открытом виде.
Для асимметричного шифрования используется пара ключей:
import nacl from "tweetnacl";
import util from "tweetnacl-util";
const keyPair = nacl.box.keyPair();
const publicKey = keyPair.publicKey;
const secretKey = keyPair.secretKey;
Публичный ключ может быть безопасно передан другим участникам системы или сохранён в базе данных. Приватный ключ должен храниться только на стороне владельца и никогда не попадать в хранилище.
TweetNaCl работает с Uint8Array, поэтому текстовые
данные необходимо конвертировать:
const message = {
userId: 123,
email: "user@example.com",
note: "Секретная информация"
};
const messageString = JSON.stringify(message);
const messageUint8 = util.decodeUTF8(messageString);
На этом этапе данные уже готовы к криптографической обработке.
Nonce (number used once) — обязательный элемент при использовании
nacl.box. Он обеспечивает уникальность каждого шифрования
даже при одинаковых входных данных.
const nonce = nacl.randomBytes(nacl.box.nonceLength);
Критически важно: повторное использование nonce с одним и тем же ключом полностью компрометирует безопасность.
Основной процесс шифрования выглядит следующим образом:
const encrypted = nacl.box(
messageUint8,
nonce,
publicKey,
secretKey
);
Результатом является Uint8Array, содержащий
зашифрованные данные.
Для хранения в базе данных данные обычно кодируются в Base64:
const encryptedBase64 = util.encodeBase64(encrypted);
const nonceBase64 = util.encodeBase64(nonce);
Типичная структура записи в базе данных может выглядеть следующим образом:
const dbRecord = {
userId: 123,
data: encryptedBase64,
nonce: nonceBase64,
publicKey: util.encodeBase64(publicKey)
};
Важно понимать, что:
data — зашифрованный payloadnonce — обязательный параметр для расшифровкиpublicKey — используется для проверки или
взаимодействия, но не для расшифровкиПри извлечении данных процесс выполняется в обратном порядке:
const decrypted = nacl.box.open(
util.decodeBase64(dbRecord.data),
util.decodeBase64(dbRecord.nonce),
util.decodeBase64(dbRecord.publicKey),
secretKey
);
const decryptedMessage = util.encodeUTF8(decrypted);
const originalObject = JSON.parse(decryptedMessage);
Если данные были изменены или ключи не совпадают, результатом будет
null.
Для сценариев, где нет необходимости в публичных ключах, используется более простая схема:
const key = nacl.randomBytes(nacl.secretbox.keyLength);
const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
const encrypted = nacl.secretbox(messageUint8, nonce, key);
Хранение:
const record = {
data: util.encodeBase64(encrypted),
nonce: util.encodeBase64(nonce),
key: util.encodeBase64(key)
};
Симметричное шифрование быстрее, но требует аккуратного обращения с ключом, так как его компрометация раскрывает все данные.
При проектировании структуры базы данных важно учитывать:
Типичная ошибка — попытка фильтровать зашифрованные данные на уровне SQL без дополнительной логики.
TweetNaCl не ограничивает структуру данных. Любой объект перед шифрованием сериализуется:
const payload = {
messages: [
{ text: "Первое сообщение" },
{ text: "Второе сообщение" }
],
timestamp: Date.now()
};
const encoded = util.decodeUTF8(JSON.stringify(payload));
После расшифровки структура восстанавливается через
JSON.parse.
Повторное использование nonce
Даже при разных сообщениях использование одного nonce приводит к уязвимостям.
Хранение приватного ключа в базе
Это полностью разрушает модель безопасности асимметричного шифрования.
Отсутствие проверки целостности данных
nacl.box обеспечивает аутентифицированное шифрование, но при ручной модификации схемы легко потерять гарантию целостности.
Неправильная кодировка
TweetNaCl работает только с байтовыми массивами, любые строковые
операции должны выполняться через tweetnacl-util.
В реальных приложениях часто используется следующая архитектура:
Клиент шифрует данные перед отправкой
Сервер хранит ciphertext без расшифровки
Расшифровка выполняется либо:
Такой подход снижает риск утечки данных даже при компрометации базы данных.
TweetNaCl.js разработан как компактная библиотека, поэтому:
Однако при больших объёмах данных стоит учитывать:
В зрелых проектах шифрование перед записью в базу выделяется в отдельный слой:
class CryptoService {
constructor(keyPair) {
this.keyPair = keyPair;
}
encrypt(message) {
const nonce = nacl.randomBytes(nacl.box.nonceLength);
const messageUint8 = util.decodeUTF8(JSON.stringify(message));
const encrypted = nacl.box(
messageUint8,
nonce,
this.keyPair.publicKey,
this.keyPair.secretKey
);
return {
data: util.encodeBase64(encrypted),
nonce: util.encodeBase64(nonce)
};
}
decrypt(data, nonce, publicKey) {
const decrypted = nacl.box.open(
util.decodeBase64(data),
util.decodeBase64(nonce),
util.decodeBase64(publicKey),
this.keyPair.secretKey
);
if (!decrypted) return null;
return JSON.parse(util.encodeUTF8(decrypted));
}
}
Такой подход позволяет изолировать криптографию от бизнес-логики приложения.
Шифрование перед записью в базу данных особенно востребовано в:
Во всех этих случаях ключевым фактором становится невозможность чтения данных даже при прямом доступе к базе.
При использовании TweetNaCl.js важно поддерживать строгую дисциплину:
Любое отклонение от этих принципов снижает криптографическую стойкость системы независимо от качества алгоритма.