Работа с файлами в браузере начинается с получения бинарных данных,
поскольку криптографические операции в TweetNaCl.js выполняются над
массивами байтов. Любой файл, будь то изображение, архив или документ,
перед шифрованием должен быть преобразован в формат
Uint8Array, с которым библиотека работает напрямую.
Браузер предоставляет File API, позволяющий получать доступ к
содержимому файлов через объекты File и Blob.
Эти объекты не содержат байтов в явном виде до момента чтения, поэтому
требуется асинхронное извлечение данных.
Основной инструмент для этого — FileReader.
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
const arrayBuffer = await file.arrayBuffer();
const data = new Uint8Array(arrayBuffer);
console.log(data);
});
Метод arrayBuffer() является предпочтительным, поскольку
он сразу возвращает бинарное представление без промежуточных
преобразований в строки.
TweetNaCl.js — компактная криптографическая библиотека, реализующая NaCl (Networking and Cryptography library). Она оперирует только байтовыми массивами и предоставляет два основных подхода к шифрованию:
nacl.secretbox)nacl.box)Для работы с файлами чаще используется secretbox, так
как он проще и быстрее.
ciphertext = nacl.secretbox(message, nonce, key)
plaintext = nacl.secretbox.open(ciphertext, nonce, key)
message — исходные данные (Uint8Array)nonce — уникальное значение (24 байта)key — секретный ключ (32 байта)Ключи должны быть криптографически случайными. TweetNaCl предоставляет генератор:
const key = nacl.randomBytes(32);
const nonce = nacl.randomBytes(24);
Ключ можно сохранять локально (например, в IndexedDB), но nonce никогда не должен повторяться для одной и той же пары ключ/сообщение.
После получения файла и преобразования его в Uint8Array,
можно применить secretbox.
async function encryptFile(file, key) {
const arrayBuffer = await file.arrayBuffer();
const data = new Uint8Array(arrayBuffer);
const nonce = nacl.randomBytes(24);
const encrypted = nacl.secretbox(data, nonce, key);
return {
encrypted,
nonce
};
}
Здесь важно учитывать, что результат шифрования — это тоже
Uint8Array, который не содержит метаданных. Поэтому nonce
необходимо сохранять отдельно.
В практических сценариях nonce часто конкатенируется с шифротекстом:
function combineNonceAndCipher(nonce, ciphertext) {
const result = new Uint8Array(nonce.length + ciphertext.length);
result.set(nonce, 0);
result.set(ciphertext, nonce.length);
return result;
}
Такой подход упрощает хранение и передачу данных, поскольку для расшифрования достаточно отделить первые 24 байта.
Процесс обратный: извлекается nonce, затем выполняется
secretbox.open.
function decryptFile(combined, key) {
const nonce = combined.slice(0, 24);
const ciphertext = combined.slice(24);
const decrypted = nacl.secretbox.open(ciphertext, nonce, key);
if (!decrypted) {
throw new Error('Ошибка расшифрования');
}
return decrypted;
}
Если ключ или данные неверны, функция возвращает null,
что служит индикатором неудачи.
После шифрования данные можно сохранить как Blob:
function saveFile(data, filename) {
const blob = new Blob([data], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
Такой подход позволяет сохранять как зашифрованные, так и расшифрованные данные.
Общий поток обработки включает:
async function processFile(file, key) {
const buffer = await file.arrayBuffer();
const data = new Uint8Array(buffer);
const nonce = nacl.randomBytes(24);
const encrypted = nacl.secretbox(data, nonce, key);
const combined = new Uint8Array(nonce.length + encrypted.length);
combined.set(nonce);
combined.set(encrypted, nonce.length);
return combined;
}
При использовании File API и TweetNaCl.js необходимо учитывать ряд ограничений:
Uint8ArrayДля больших файлов используется разбиение на чанки, однако это
усложняет криптографическую модель, поскольку secretbox не
является потоковым алгоритмом.
При необходимости можно разделить файл:
const CHUNK_SIZE = 64 * 1024;
function splitIntoChunks(data) {
const chunks = [];
for (let i = 0; i < data.length; i += CHUNK_SIZE) {
chunks.push(data.slice(i, i + CHUNK_SIZE));
}
return chunks;
}
Каждый чанк шифруется отдельно с собственным nonce, что увеличивает объём служебных данных, но позволяет избежать переполнения памяти.
Nonce в TweetNaCl.js не должен повторяться при одном и том же ключе. Повтор nonce приводит к компрометации данных из-за свойств поточного шифра.
Практическая схема:
Интеграция File API в пользовательский интерфейс обычно строится вокруг событий:
change для inputdragover и drop для drag-and-dropdropZone.addEventListener('drop', async (event) => {
event.preventDefault();
const file = event.dataTransfer.files[0];
const encrypted = await processFile(file, key);
saveFile(encrypted, 'encrypted.bin');
});
Drag-and-drop позволяет обрабатывать файлы без промежуточного сохранения.
Хотя TweetNaCl.js работает с байтами, иногда требуется представление в строковом виде (например, для хранения в JSON или отправки через API). Используется Base64:
function toBase64(uint8) {
return btoa(String.fromCharCode(...uint8));
}
function fromBase64(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
Base64 увеличивает размер данных примерно на 33%, что следует учитывать при хранении.
Для долговременного хранения ключей используется IndexedDB:
async function storeKey(key) {
const db = indexedDB.open('crypto-db', 1);
db.onsucc ess = () => {
const tx = db.result.transaction('keys', 'readwrite');
const store = tx.objectStore('keys');
store.put(key, 'main-key');
};
}
Прямое хранение в localStorage нежелательно из-за ограничений и отсутствия бинарной поддержки.
Uint8ArrayЭти ошибки приводят либо к невозможности расшифрования, либо к уязвимостям криптосистемы.
TweetNaCl.js оптимизирован для компактности, а не для максимальной скорости. При работе с File API основное узкое место — копирование массивов и операции с памятью.
Оптимизация достигается через:
Uint8ArrayРабота с файлами в браузере через File API в связке с TweetNaCl.js формирует устойчивую модель локального шифрования, в которой данные никогда не покидают клиентскую среду в открытом виде, а все операции выполняются над низкоуровневыми бинарными структурами.