Работа с файловой системой в Node.js в связке с криптографическими алгоритмами чаще всего сводится к задаче преобразования бинарных данных в зашифрованный формат и последующего восстановления исходного содержимого при наличии ключа. Библиотека CryptoJS предоставляет реализацию симметричных алгоритмов шифрования, включая AES, а также инструменты для хеширования и генерации ключей.
Шифрование файлов на диске строится вокруг последовательности операций:
При расшифровке выполняется обратная последовательность операций.
Ключевой особенностью CryptoJS является работа с данными в формате WordArray, поэтому любые бинарные данные должны быть корректно преобразованы.
Для использования CryptoJS в Node.js достаточно установить пакет:
npm install crypto-js
Дополнительно используется встроенный модуль файловой системы:
const fs = require('fs');
const CryptoJS = require('crypto-js');
Файлы в Node.js представлены Buffer. CryptoJS работает с WordArray, поэтому требуется преобразование:
function bufferToWordArray(buffer) {
return CryptoJS.lib.WordArray.create(buffer);
}
function wordArrayToBuffer(wordArray) {
const words = wordArray.words;
const sigBytes = wordArray.sigBytes;
const buffer = Buffer.alloc(sigBytes);
let offset = 0;
for (let i = 0; i < words.length; i++) {
const word = words[i];
buffer[offset++] = (word >> 24) & 0xff;
buffer[offset++] = (word >> 16) & 0xff;
buffer[offset++] = (word >> 8) & 0xff;
buffer[offset++] = word & 0xff;
}
return buffer;
}
Использование прямого ключа считается небезопасным, поэтому применяется PBKDF2:
function deriveKey(password, salt) {
return CryptoJS.PBKDF2(password, salt, {
keySize: 256 / 32,
iterations: 10000
});
}
Salt должен быть случайным:
function generateSalt() {
return CryptoJS.lib.WordArray.random(128 / 8);
}
AES используется в режиме CBC с случайным IV:
function generateIv() {
return CryptoJS.lib.WordArray.random(128 / 8);
}
Шифрование выполняется так:
function encryptData(wordArray, key, iv) {
return CryptoJS.AES.encrypt(wordArray, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
}
Для корректного восстановления данных необходимо сохранять:
Удобный формат — объединение всех частей в base64:
function serializeEncrypted(salt, iv, ciphertext) {
return JSON.stringify({
salt: CryptoJS.enc.Base64.stringify(salt),
iv: CryptoJS.enc.Base64.stringify(iv),
data: ciphertext.toString()
});
}
Полный процесс шифрования:
function encryptFile(inputPath, outputPath, password) {
const fileBuffer = fs.readFileSync(inputPath);
const wordArray = bufferToWordArray(fileBuffer);
const salt = generateSalt();
const iv = generateIv();
const key = deriveKey(password, salt);
const encrypted = encryptData(wordArray, key, iv);
const payload = serializeEncrypted(salt, iv, encrypted);
fs.writeFileSync(outputPath, payload, 'utf8');
}
Обратный процесс требует восстановления всех параметров:
function decryptFile(inputPath, outputPath, password) {
const raw = fs.readFileSync(inputPath, 'utf8');
const payload = JSON.parse(raw);
const salt = CryptoJS.enc.Base64.parse(payload.salt);
const iv = CryptoJS.enc.Base64.parse(payload.iv);
const ciphertext = payload.data;
const key = deriveKey(password, salt);
const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
const buffer = wordArrayToBuffer(decrypted);
fs.writeFileSync(outputPath, buffer);
}
CryptoJS не поддерживает потоковое шифрование, поэтому при работе с большими файлами возникает ограничение памяти.
Используются следующие подходы:
Простейшая реализация блочного подхода:
const CHUNK_SIZE = 1024 * 1024; // 1MB
function encryptLargeFile(inputPath, outputPath, password) {
const buffer = fs.readFileSync(inputPath);
const salt = generateSalt();
const iv = generateIv();
const key = deriveKey(password, salt);
const chunks = [];
for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) {
const chunk = buffer.slice(offset, offset + CHUNK_SIZE);
const wordArray = bufferToWordArray(chunk);
const encrypted = CryptoJS.AES.encrypt(wordArray, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
chunks.push(encrypted.toString());
}
const payload = {
salt: CryptoJS.enc.Base64.stringify(salt),
iv: CryptoJS.enc.Base64.stringify(iv),
chunks
};
fs.writeFileSync(outputPath, JSON.stringify(payload));
}
function decryptLargeFile(inputPath, outputPath, password) {
const raw = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
const salt = CryptoJS.enc.Base64.parse(raw.salt);
const iv = CryptoJS.enc.Base64.parse(raw.iv);
const key = deriveKey(password, salt);
const buffers = [];
for (const chunk of raw.chunks) {
const decrypted = CryptoJS.AES.decrypt(chunk, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
buffers.push(wordArrayToBuffer(decrypted));
}
fs.writeFileSync(outputPath, Buffer.concat(buffers));
}
Критически важные моменты при работе с файловым шифрованием:
Часто возникающие проблемы:
encryptFile('input.txt', 'encrypted.json', 'strong-password');
decryptFile('encrypted.json', 'output.txt', 'strong-password');
CryptoJS реализован как библиотека для работы в памяти, поэтому:
При необходимости обработки больших объемов данных рациональнее
использовать модуль crypto из Node.js, однако CryptoJS
остается удобным в задачах совместимости и учебных сценариях.