UNPKG

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
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;