securecrypt
Version:
SecureCrypt: High-performance Node.js encryption library. Provides AES-256-GCM encryption, PBKDF2-based key derivation, secure file & text encryption, password generation, and multi-threaded processing for maximum speed and security.
985 lines (823 loc) • 36.3 kB
JavaScript
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
class SecureCrypt {
static VERSION = '0.0.6';
static ALGORITHM = 'aes-256-gcm';
static KEY_LENGTH = 32;
static IV_LENGTH = 16;
static TAG_LENGTH = 16;
static SALT_LENGTH = 64;
static DEFAULT_ITERATIONS = 100000;
static CHUNK_SIZE = 64 * 1024;
/**
* Generate cryptographically secure random bytes
* @param {number} length - Number of bytes to generate
* @returns {Buffer} Random bytes
*/
static generateSecureRandom(length) {
return crypto.randomBytes(length);
}
/**
* Derive key from password using PBKDF2
* @param {string} password - Password to derive key from
* @param {Buffer} salt - Salt for key derivation
* @param {number} iterations - Number of PBKDF2 iterations
* @returns {Buffer} Derived key
*/
static deriveKey(password, salt, iterations = this.DEFAULT_ITERATIONS) {
return crypto.pbkdf2Sync(password, salt, iterations, this.KEY_LENGTH, 'sha512');
}
/**
* Derive key asynchronously
* @param {string} password - Password to derive key from
* @param {Buffer} salt - Salt for key derivation
* @param {number} iterations - Number of PBKDF2 iterations
* @returns {Promise<Buffer>} Derived key
*/
static async deriveKeyAsync(password, salt, iterations = this.DEFAULT_ITERATIONS) {
const pbkdf2 = promisify(crypto.pbkdf2);
return await pbkdf2(password, salt, iterations, this.KEY_LENGTH, 'sha512');
}
/**
* Encrypt data using AES-256-GCM
* @param {Buffer} data - Data to encrypt
* @param {Buffer} key - Encryption key
* @param {Buffer} iv - Initialization vector
* @param {Buffer} additionalData - Additional authenticated data (optional)
* @returns {Object} Encrypted data with authentication tag
*/
static encryptData(data, key, iv, additionalData = null) {
const cipher = crypto.createCipheriv(this.ALGORITHM, key, iv);
cipher.setAAD(additionalData || Buffer.alloc(0));
const encrypted = Buffer.concat([
cipher.update(data),
cipher.final()
]);
const tag = cipher.getAuthTag();
return { encrypted, tag };
}
/**
* Decrypt data using AES-256-GCM
* @param {Buffer} encryptedData - Encrypted data
* @param {Buffer} key - Decryption key
* @param {Buffer} iv - Initialization vector
* @param {Buffer} tag - Authentication tag
* @param {Buffer} additionalData - Additional authenticated data (optional)
* @returns {Buffer} Decrypted data
*/
static decryptData(encryptedData, key, iv, tag, additionalData = null) {
const decipher = crypto.createDecipheriv(this.ALGORITHM, key, iv);
decipher.setAAD(additionalData || Buffer.alloc(0));
decipher.setAuthTag(tag);
return Buffer.concat([
decipher.update(encryptedData),
decipher.final()
]);
}
/**
* Create encrypted file header
* @param {Buffer} salt - Salt used for key derivation
* @param {Buffer} iv - Initialization vector
* @param {number} iterations - PBKDF2 iterations
* @returns {Buffer} File header
*/
static createFileHeader(salt, iv, iterations) {
const header = Buffer.alloc(4 + 4 + this.SALT_LENGTH + this.IV_LENGTH);
let offset = 0;
header.writeUInt32BE(0x53454352, offset); // 'SECR'
offset += 4;
header.writeUInt32BE(iterations, offset);
offset += 4;
salt.copy(header, offset);
offset += this.SALT_LENGTH;
iv.copy(header, offset);
return header;
}
/**
* Parse encrypted file header
* @param {Buffer} headerData - Header data
* @returns {Object} Parsed header information
*/
static parseFileHeader(headerData) {
if (headerData.length < 8 + this.SALT_LENGTH + this.IV_LENGTH) {
throw new Error('Invalid file header: insufficient data');
}
let offset = 0;
const magic = headerData.readUInt32BE(offset);
if (magic !== 0x53454352) {
throw new Error('Invalid file format: not a SecureCrypt file');
}
offset += 4;
const iterations = headerData.readUInt32BE(offset);
offset += 4;
const salt = headerData.slice(offset, offset + this.SALT_LENGTH);
offset += this.SALT_LENGTH;
const iv = headerData.slice(offset, offset + this.IV_LENGTH);
return { salt, iv, iterations };
}
/**
* Encrypt file with progress callback
* @param {string} inputPath - Path to input file
* @param {string} outputPath - Path to output file
* @param {string} password - Encryption password
* @param {Object} options - Encryption options
* @returns {Promise<Object>} Encryption result
*/
static async encryptFile(inputPath, outputPath, password, options = {}) {
const startTime = process.hrtime.bigint();
return new Promise((resolve, reject) => {
try {
// Validate input
if (!fs.existsSync(inputPath)) {
throw new Error('Input file does not exist');
}
const inputStats = fs.statSync(inputPath);
if (!inputStats.isFile()) {
throw new Error('Input path is not a file');
}
// Generate cryptographic parameters
const salt = this.generateSecureRandom(this.SALT_LENGTH);
const iv = this.generateSecureRandom(this.IV_LENGTH);
const iterations = options.iterations || this.DEFAULT_ITERATIONS;
// Derive key
const key = this.deriveKey(password, salt, iterations);
// Create output directory if needed
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Add .secrypt extension if not present
const finalOutputPath = outputPath.endsWith('.secrypt') ?
outputPath : outputPath + '.secrypt';
// Create streams
const readStream = fs.createReadStream(inputPath, { highWaterMark: this.CHUNK_SIZE });
const writeStream = fs.createWriteStream(finalOutputPath);
const aad = Buffer.from(path.basename(inputPath, '.secrypt'));
const cipher = crypto.createCipheriv(this.ALGORITHM, key, iv);
cipher.setAAD(aad);
// Write header
const header = this.createFileHeader(salt, iv, iterations);
writeStream.write(header);
let totalBytesProcessed = 0;
const fileSize = inputStats.size;
// Handle data chunks
readStream.on('data', (chunk) => {
const encryptedChunk = cipher.update(chunk);
writeStream.write(encryptedChunk);
totalBytesProcessed += chunk.length;
// Progress callback
if (options.onProgress) {
options.onProgress({
bytesProcessed: totalBytesProcessed,
totalBytes: fileSize,
percentage: (totalBytesProcessed / fileSize) * 100
});
}
});
// Handle stream end
readStream.on('end', () => {
try {
const finalChunk = cipher.final();
const tag = cipher.getAuthTag();
writeStream.write(finalChunk);
writeStream.write(tag);
writeStream.end();
} catch (error) {
reject(new Error(`Encryption failed: ${error.message}`));
}
});
// Handle write completion
writeStream.on('finish', () => {
const endTime = process.hrtime.bigint();
const processingTime = Number(endTime - startTime) / 1000000;
resolve({
success: true,
inputFile: inputPath,
outputFile: finalOutputPath,
inputSize: fileSize,
outputSize: fs.statSync(finalOutputPath).size,
processingTime: processingTime,
algorithm: this.ALGORITHM,
iterations: iterations
});
});
// Error handlers
readStream.on('error', (error) => {
reject(new Error(`Read error: ${error.message}`));
});
writeStream.on('error', (error) => {
reject(new Error(`Write error: ${error.message}`));
});
} catch (error) {
reject(new Error(`Encryption setup failed: ${error.message}`));
}
});
}
/**
* Decrypt file with progress callback
* @param {string} inputPath - Path to encrypted file
* @param {string} outputPath - Path to output file
* @param {string} password - Decryption password
* @param {Object} options - Decryption options
* @returns {Promise<Object>} Decryption result
*/
static async decryptFile(inputPath, outputPath, password, options = {}) {
const startTime = process.hrtime.bigint();
return new Promise((resolve, reject) => {
try {
// Validate input
if (!fs.existsSync(inputPath)) {
throw new Error('Encrypted file does not exist');
}
const inputStats = fs.statSync(inputPath);
if (!inputStats.isFile()) {
throw new Error('Input path is not a file');
}
// Read and parse header
const headerSize = 8 + this.SALT_LENGTH + this.IV_LENGTH;
const headerBuffer = Buffer.alloc(headerSize);
const fd = fs.openSync(inputPath, 'r');
fs.readSync(fd, headerBuffer, 0, headerSize, 0);
const { salt, iv, iterations } = this.parseFileHeader(headerBuffer);
// Derive key
const key = this.deriveKey(password, salt, iterations);
// Calculate encrypted data size
const encryptedDataSize = inputStats.size - headerSize - this.TAG_LENGTH;
// Read authentication tag
const tagBuffer = Buffer.alloc(this.TAG_LENGTH);
fs.readSync(fd, tagBuffer, 0, this.TAG_LENGTH, inputStats.size - this.TAG_LENGTH);
fs.closeSync(fd);
// Create output directory if needed
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Create streams
const readStream = fs.createReadStream(inputPath, {
start: headerSize,
end: inputStats.size - this.TAG_LENGTH - 1,
highWaterMark: this.CHUNK_SIZE
});
const writeStream = fs.createWriteStream(outputPath);
// Use exactly the same AAD as encryption
const originalFileName = path.basename(inputPath, '.secrypt');
const aad = Buffer.from(originalFileName);
// Create decipher - IMPORTANT: setAAD must be called before setAuthTag
const decipher = crypto.createDecipheriv(this.ALGORITHM, key, iv);
decipher.setAAD(aad);
decipher.setAuthTag(tagBuffer);
let totalBytesProcessed = 0;
// Handle data chunks
readStream.on('data', (chunk) => {
try {
const decryptedChunk = decipher.update(chunk);
writeStream.write(decryptedChunk);
totalBytesProcessed += chunk.length;
// Progress callback
if (options.onProgress) {
options.onProgress({
bytesProcessed: totalBytesProcessed,
totalBytes: encryptedDataSize,
percentage: (totalBytesProcessed / encryptedDataSize) * 100
});
}
} catch (error) {
reject(new Error(`Decryption failed: ${error.message}`));
}
});
// Handle stream end
readStream.on('end', () => {
try {
const finalChunk = decipher.final();
writeStream.write(finalChunk);
writeStream.end();
} catch (error) {
reject(new Error(`Decryption verification failed: ${error.message}`));
}
});
// Handle write completion
writeStream.on('finish', () => {
const endTime = process.hrtime.bigint();
const processingTime = Number(endTime - startTime) / 1000000;
resolve({
success: true,
inputFile: inputPath,
outputFile: outputPath,
inputSize: inputStats.size,
outputSize: fs.statSync(outputPath).size,
processingTime: processingTime,
algorithm: this.ALGORITHM,
iterations: iterations
});
});
// Error handlers
readStream.on('error', (error) => {
reject(new Error(`Read error: ${error.message}`));
});
writeStream.on('error', (error) => {
reject(new Error(`Write error: ${error.message}`));
});
} catch (error) {
reject(new Error(`Decryption setup failed: ${error.message}`));
}
});
}
/**
* Encrypt text string
* @param {string} text - Text to encrypt
* @param {string} password - Encryption password
* @param {Object} options - Encryption options
* @returns {Object} Encryption result with base64 encoded data
*/
static encryptText(text, password, options = {}) {
try {
const textBuffer = Buffer.from(text, 'utf8');
const salt = this.generateSecureRandom(this.SALT_LENGTH);
const iv = this.generateSecureRandom(this.IV_LENGTH);
const iterations = options.iterations || this.DEFAULT_ITERATIONS;
// Derive key
const key = this.deriveKey(password, salt, iterations);
// Encrypt
const cipher = crypto.createCipheriv(this.ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(textBuffer),
cipher.final()
]);
const tag = cipher.getAuthTag();
// Create result buffer
const resultBuffer = Buffer.concat([
Buffer.from([0x53, 0x45, 0x43, 0x52]), // Magic number
Buffer.from([iterations >>> 24, (iterations >>> 16) & 0xFF, (iterations >>> 8) & 0xFF, iterations & 0xFF]),
salt,
iv,
tag,
encrypted
]);
return {
success: true,
encryptedData: resultBuffer.toString('base64'),
algorithm: this.ALGORITHM,
iterations: iterations
};
} catch (error) {
return {
success: false,
error: `Text encryption failed: ${error.message}`
};
}
}
/**
* Decrypt text string
* @param {string} encryptedText - Base64 encoded encrypted text
* @param {string} password - Decryption password
* @param {Object} options - Decryption options
* @returns {Object} Decryption result
*/
static decryptText(encryptedText, password, options = {}) {
try {
const encryptedBuffer = Buffer.from(encryptedText, 'base64');
if (encryptedBuffer.length < 8 + this.SALT_LENGTH + this.IV_LENGTH + this.TAG_LENGTH) {
throw new Error('Invalid encrypted text format');
}
let offset = 0;
// Check magic number
const magic = encryptedBuffer.readUInt32BE(offset);
if (magic !== 0x53454352) {
throw new Error('Invalid encrypted text format');
}
offset += 4;
// Read iterations
const iterations = encryptedBuffer.readUInt32BE(offset);
offset += 4;
// Read salt
const salt = encryptedBuffer.slice(offset, offset + this.SALT_LENGTH);
offset += this.SALT_LENGTH;
// Read IV
const iv = encryptedBuffer.slice(offset, offset + this.IV_LENGTH);
offset += this.IV_LENGTH;
// Read tag
const tag = encryptedBuffer.slice(offset, offset + this.TAG_LENGTH);
offset += this.TAG_LENGTH;
// Read encrypted data
const encrypted = encryptedBuffer.slice(offset);
// Derive key
const key = this.deriveKey(password, salt, iterations);
// Decrypt
const decipher = crypto.createDecipheriv(this.ALGORITHM, key, iv);
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]);
return {
success: true,
decryptedText: decrypted.toString('utf8'),
algorithm: this.ALGORITHM,
iterations: iterations
};
} catch (error) {
return {
success: false,
error: `Text decryption failed: ${error.message}`
};
}
}
/**
* Generate cryptographically secure password
* @param {number} length - Password length (minimum 12)
* @param {Object} options - Password generation options
* @returns {string} Generated password
*/
static generateSecurePassword(length = 32, options = {}) {
if (length < 12) {
throw new Error('Password length must be at least 12 characters');
}
const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const lowercase = 'abcdefghijklmnopqrstuvwxyz';
const numbers = '0123456789';
const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?';
let charset = '';
let requiredChars = '';
if (options.excludeUppercase !== true) {
charset += uppercase;
requiredChars += uppercase[crypto.randomInt(uppercase.length)];
}
if (options.excludeLowercase !== true) {
charset += lowercase;
requiredChars += lowercase[crypto.randomInt(lowercase.length)];
}
if (options.excludeNumbers !== true) {
charset += numbers;
requiredChars += numbers[crypto.randomInt(numbers.length)];
}
if (options.excludeSymbols !== true) {
charset += symbols;
requiredChars += symbols[crypto.randomInt(symbols.length)];
}
if (charset.length === 0) {
throw new Error('At least one character type must be enabled');
}
// Generate remaining characters
let password = requiredChars;
for (let i = requiredChars.length; i < length; i++) {
password += charset[crypto.randomInt(charset.length)];
}
// Shuffle password
const passwordArray = password.split('');
for (let i = passwordArray.length - 1; i > 0; i--) {
const j = crypto.randomInt(i + 1);
[passwordArray[i], passwordArray[j]] = [passwordArray[j], passwordArray[i]];
}
return passwordArray.join('');
}
/**
* Validate password strength
* @param {string} password - Password to validate
* @returns {Object} Validation result with score and feedback
*/
static validatePassword(password) {
const result = {
isValid: false,
score: 0,
feedback: []
};
if (password.length < 8) {
result.feedback.push('Password must be at least 8 characters long');
} else if (password.length >= 12) {
result.score += 2;
} else {
result.score += 1;
}
if (/[A-Z]/.test(password)) {
result.score += 1;
} else {
result.feedback.push('Password should contain uppercase letters');
}
if (/[a-z]/.test(password)) {
result.score += 1;
} else {
result.feedback.push('Password should contain lowercase letters');
}
if (/\d/.test(password)) {
result.score += 1;
} else {
result.feedback.push('Password should contain numbers');
}
if (/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(password)) {
result.score += 1;
} else {
result.feedback.push('Password should contain special characters');
}
if (password.length >= 16) {
result.score += 1;
}
if (!/(.)\1{2,}/.test(password)) {
result.score += 1;
} else {
result.feedback.push('Password should not contain repeated characters');
}
result.isValid = result.score >= 5 && password.length >= 8;
if (result.score <= 2) {
result.strength = 'Very Weak';
} else if (result.score <= 4) {
result.strength = 'Weak';
} else if (result.score <= 6) {
result.strength = 'Good';
} else if (result.score <= 7) {
result.strength = 'Strong';
} else {
result.strength = 'Very Strong';
}
return result;
}
/**
* Calculate file hash for integrity verification
* @param {string} filePath - Path to file
* @param {string} algorithm - Hash algorithm (default: sha256)
* @returns {Promise<string>} File hash
*/
static async calculateFileHash(filePath, algorithm = 'sha256') {
return new Promise((resolve, reject) => {
const hash = crypto.createHash(algorithm);
const stream = fs.createReadStream(filePath);
stream.on('data', (chunk) => {
hash.update(chunk);
});
stream.on('end', () => {
resolve(hash.digest('hex'));
});
stream.on('error', (error) => {
reject(error);
});
});
}
/**
* Securely wipe file by overwriting with random data
* @param {string} filePath - Path to file to wipe
* @param {number} passes - Number of overwrite passes (default: 3)
* @returns {Promise<boolean>} Success status
*/
static async secureWipeFile(filePath, passes = 3) {
try {
if (!fs.existsSync(filePath)) {
throw new Error('File does not exist');
}
const stats = fs.statSync(filePath);
const fileSize = stats.size;
for (let pass = 0; pass < passes; pass++) {
const writeStream = fs.createWriteStream(filePath);
let bytesWritten = 0;
while (bytesWritten < fileSize) {
const chunkSize = Math.min(this.CHUNK_SIZE, fileSize - bytesWritten);
const randomData = crypto.randomBytes(chunkSize);
writeStream.write(randomData);
bytesWritten += chunkSize;
}
writeStream.end();
// Wait for write to complete
await new Promise((resolve, reject) => {
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
}
// Finally delete the file
fs.unlinkSync(filePath);
return true;
} catch (error) {
throw new Error(`Secure wipe failed: ${error.message}`);
}
}
/**
* Check if file is encrypted with SecureCrypt
* @param {string} filePath - Path to file
* @returns {boolean} True if file is SecureCrypt encrypted
*/
static isEncryptedFile(filePath) {
try {
if (!fs.existsSync(filePath)) {
return false;
}
if (path.extname(filePath).toLowerCase() === '.secrypt') {
return true;
}
// Check magic number
const buffer = Buffer.alloc(4);
const fd = fs.openSync(filePath, 'r');
fs.readSync(fd, buffer, 0, 4, 0);
fs.closeSync(fd);
return buffer.readUInt32BE(0) === 0x53454352;
} catch (error) {
return false;
}
}
/**
* Get detailed file information
* @param {string} filePath - Path to file
* @returns {Object|null} File information
*/
static getFileInfo(filePath) {
try {
if (!fs.existsSync(filePath)) {
return null;
}
const stats = fs.statSync(filePath);
const isEncrypted = this.isEncryptedFile(filePath);
const info = {
path: filePath,
size: stats.size,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
encrypted: isEncrypted,
algorithm: isEncrypted ? this.ALGORITHM : null,
created: stats.birthtime,
modified: stats.mtime,
accessed: stats.atime
};
if (isEncrypted && stats.isFile()) {
try {
const headerSize = 8 + this.SALT_LENGTH + this.IV_LENGTH;
const headerBuffer = Buffer.alloc(headerSize);
const fd = fs.openSync(filePath, 'r');
fs.readSync(fd, headerBuffer, 0, headerSize, 0);
fs.closeSync(fd);
const { iterations } = this.parseFileHeader(headerBuffer);
info.iterations = iterations;
info.encryptedSize = stats.size - headerSize - this.TAG_LENGTH;
} catch (error) {
info.error = 'Could not read encryption metadata';
}
}
return info;
} catch (error) {
return null;
}
}
/**
* Benchmark encryption/decryption performance
* @param {number} dataSize - Size of test data in bytes
* @param {Object} options - Benchmark options
* @returns {Promise<Object>} Benchmark results
*/
static async benchmark(dataSize = 1024 * 1024, options = {}) {
const testData = crypto.randomBytes(dataSize);
const testPassword = 'BenchmarkPassword123!@#';
const iterations = options.iterations || 10000; // Lower for benchmarking
console.log(`Starting benchmark with ${(dataSize / 1024 / 1024).toFixed(2)}MB of data...`);
// Text encryption benchmark
const textStartTime = process.hrtime.bigint();
const encryptedResult = this.encryptText(testData.toString('hex'), testPassword, { iterations });
const textEncTime = Number(process.hrtime.bigint() - textStartTime) / 1000000;
if (!encryptedResult.success) {
throw new Error('Encryption benchmark failed');
}
const textDecStartTime = process.hrtime.bigint();
const decryptedResult = this.decryptText(encryptedResult.encryptedData, testPassword);
const textDecTime = Number(process.hrtime.bigint() - textDecStartTime) / 1000000;
if (!decryptedResult.success) {
throw new Error('Decryption benchmark failed');
}
// File encryption benchmark
const tempDir = path.join(__dirname, 'temp_benchmark');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const tempInputFile = path.join(tempDir, 'benchmark_input.dat');
const tempEncryptedFile = path.join(tempDir, 'benchmark_encrypted.secrypt');
const tempDecryptedFile = path.join(tempDir, 'benchmark_decrypted.dat');
// Write test data to file
fs.writeFileSync(tempInputFile, testData);
// File encryption
const fileEncResult = await this.encryptFile(tempInputFile, tempEncryptedFile, testPassword, { iterations });
// File decryption
const fileDecResult = await this.decryptFile(tempEncryptedFile, tempDecryptedFile, testPassword);
// Cleanup
try {
fs.unlinkSync(tempInputFile);
fs.unlinkSync(tempEncryptedFile);
fs.unlinkSync(tempDecryptedFile);
fs.rmdirSync(tempDir);
} catch (error) {
console.warn('Cleanup warning:', error.message);
}
const results = {
dataSize: {
bytes: dataSize,
mb: (dataSize / 1024 / 1024).toFixed(2)
},
textEncryption: {
time: textEncTime.toFixed(2) + 'ms',
throughput: (dataSize / (textEncTime / 1000) / 1024 / 1024).toFixed(2) + ' MB/s'
},
textDecryption: {
time: textDecTime.toFixed(2) + 'ms',
throughput: (dataSize / (textDecTime / 1000) / 1024 / 1024).toFixed(2) + ' MB/s'
},
fileEncryption: {
time: fileEncResult.processingTime.toFixed(2) + 'ms',
throughput: (dataSize / (fileEncResult.processingTime / 1000) / 1024 / 1024).toFixed(2) + ' MB/s'
},
fileDecryption: {
time: fileDecResult.processingTime.toFixed(2) + 'ms',
throughput: (dataSize / (fileDecResult.processingTime / 1000) / 1024 / 1024).toFixed(2) + ' MB/s'
},
algorithm: this.ALGORITHM,
keyDerivation: 'PBKDF2-SHA512',
iterations: iterations,
version: this.VERSION
};
console.log('Benchmark completed successfully');
return results;
}
/**
* Parallel file encryption using worker threads
* @param {Array<string>} filePaths - Array of file paths to encrypt
* @param {string} password - Encryption password
* @param {Object} options - Options including output directory
* @returns {Promise<Array>} Array of results
*/
static async encryptFilesParallel(filePaths, password, options = {}) {
const numWorkers = options.workers || require('os').cpus().length;
const chunkSize = Math.ceil(filePaths.length / numWorkers);
const workers = [];
const results = [];
for (let i = 0; i < numWorkers; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, filePaths.length);
const chunk = filePaths.slice(start, end);
if (chunk.length > 0) {
const workerPromise = new Promise((resolve, reject) => {
const worker = new Worker(__filename, {
workerData: {
action: 'encryptFiles',
files: chunk,
password: password,
options: options
}
});
worker.on('message', (result) => {
resolve(result);
});
worker.on('error', (error) => {
reject(error);
});
});
workers.push(workerPromise);
}
}
const workerResults = await Promise.all(workers);
workerResults.forEach(result => {
results.push(...result);
});
return results;
}
/**
* Get system information for optimization
* @returns {Object} System information
*/
static getSystemInfo() {
const os = require('os');
return {
platform: os.platform(),
architecture: os.arch(),
cpus: os.cpus().length,
totalMemory: Math.round(os.totalmem() / 1024 / 1024) + 'MB',
freeMemory: Math.round(os.freemem() / 1024 / 1024) + 'MB',
nodeVersion: process.version,
cryptoConstants: crypto.constants ? Object.keys(crypto.constants).length : 'N/A',
recommendedChunkSize: this.CHUNK_SIZE,
version: this.VERSION
};
}
}
// Worker thread handler for parallel processing
if (!isMainThread && workerData) {
const { action, files, password, options } = workerData;
if (action === 'encryptFiles') {
Promise.all(
files.map(async (filePath) => {
try {
const outputPath = path.join(
options.outputDir || path.dirname(filePath),
path.basename(filePath) + '.secrypt'
);
const result = await SecureCrypt.encryptFile(filePath, outputPath, password, options);
return result;
} catch (error) {
return {
success: false,
inputFile: filePath,
error: error.message
};
}
})
).then((results) => {
parentPort.postMessage(results);
}).catch((error) => {
parentPort.postMessage({ error: error.message });
});
}
}
module.exports = SecureCrypt;