UNPKG

cryptia

Version:

Cryptia is a simple JavaScript library for encrypting and decrypting text using a basic substitution cipher. It provides an easy-to-use interface for securing text data in client-side applications..

473 lines (397 loc) 13.7 kB
import path from 'path' import fs from 'fs' import { decryptionAlgorithm, encryptionAlgorithm } from './core/engine.js' import { Buffer } from 'buffer' import { createReadStream, createWriteStream } from 'fs' import { pipeline } from 'stream/promises' import { Transform } from 'stream' /** * Creates a new Cryptia instance with the specified options. * @param {Object} options - Configuration options for the Cryptia instance. * @param {number} [options.obfuscationLevel=8] - Level of encryption obfuscation (1-10). * @param {boolean} [options.logging=false] - Whether to log operations. * @param {boolean} [options.preserveWhitespace=true] - Whether to preserve whitespace in encrypted text. * @returns {Object} The Cryptia API object. */ function Cryptia (options = {}) { const defaultOptions = { obfuscationLevel: 8, logging: false, preserveWhitespace: true, enableProgressTracking: false } const settings = { ...defaultOptions, ...options } function log (message) { if (settings.logging) { console.log(`[-cryptia] ${message}`) } } function measureTime (operation) { const start = performance.now() const result = operation() const end = performance.now() const timeTaken = end - start log(`Operation completed in ${timeTaken.toFixed(4)} milliseconds`) return { result, timeTaken } } function checkKeyStrength(key) { if (!key || typeof key !== 'string') { return { isStrong: false, score: 0, message: 'No key provided' } } let score = 0 const messages = [] // Length check if (key.length < 8) { messages.push('Key should be at least 8 characters long') } else { score += Math.min(key.length / 2, 5) // Up to 5 points for length } // Character variety checks if (/[A-Z]/.test(key)) score += 1 else messages.push('Consider adding uppercase letters') if (/[a-z]/.test(key)) score += 1 else messages.push('Consider adding lowercase letters') if (/[0-9]/.test(key)) score += 1 else messages.push('Consider adding numbers') if (/[^A-Za-z0-9]/.test(key)) score += 2 else messages.push('Consider adding special characters') const isStrong = score >= 6 const strengthMessage = messages.join('. ') return { isStrong, score, message: strengthMessage || 'Key strength is good', warning: !isStrong } } function createProgressTracker() { const listeners = [] let progress = 0 return { update: (percentage) => { progress = percentage listeners.forEach(listener => listener(percentage)) }, onProgress: (listener) => { listeners.push(listener) // Return unsubscribe function return () => { const index = listeners.indexOf(listener) if (index > -1) { listeners.splice(index, 1) } } }, getCurrentProgress: () => progress } } function encrypt (text, key, callback = null, options = {}) { if (!key || typeof key !== 'string') { throw new Error('A valid key is required for encryption.') } const progressTracker = settings.enableProgressTracking ? createProgressTracker() : null const { result: encryptedText, timeTaken } = measureTime(() => { // For longer texts, report progress if (progressTracker && text.length > 1000) { const chunkSize = 100 const totalChunks = Math.ceil(text.length / chunkSize) let encryptedParts = [] for (let i = 0; i < totalChunks; i++) { const start = i * chunkSize const end = Math.min(start + chunkSize, text.length) const chunk = text.substring(start, end) const encryptedChunk = encryptionAlgorithm( chunk, key, settings.obfuscationLevel, settings.preserveWhitespace ) encryptedParts.push(encryptedChunk) // Update progress progressTracker.update((i + 1) / totalChunks * 100) } return encryptedParts.join('') } else { return encryptionAlgorithm( text, key, settings.obfuscationLevel, settings.preserveWhitespace ) } }) const result = { data: encryptedText, timeTaken, onProgress: progressTracker?.onProgress } if (callback && typeof callback === 'function') { callback(result) } return result } function decrypt (encryptedText, key, callback = null) { if (!key || typeof key !== 'string') { throw new Error('A valid key is required for decryption.') } const { result: decryptedText, timeTaken } = measureTime(() => decryptionAlgorithm( encryptedText, key, settings.obfuscationLevel, settings.preserveWhitespace ) ) const result = { data: decryptedText, timeTaken } if (callback && typeof callback === 'function') { callback(result) } return result } function encryptFile ( filePath, key, callback = null, outputFileName = null ) { try { if (!fs.existsSync(filePath)) { throw new Error(`File not found: ${filePath}`) } const fileContent = fs.readFileSync(filePath, 'utf-8') const { data: encryptedContent, timeTaken } = encrypt(fileContent, key) // Determine output path const encryptedFilePath = path.isAbsolute(outputFileName) ? outputFileName : path.join(path.dirname(filePath), outputFileName) // Use a better default filename based on the input if (!outputFileName) { const originalName = path.basename(filePath) outputFileName = `${originalName}.encrypted` } // Create directory if it doesn't exist const outputDir = path.dirname(encryptedFilePath) if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }) } // Write encrypted content fs.writeFileSync(encryptedFilePath, encryptedContent, 'utf-8') log(`File encryption completed in ${timeTaken.toFixed(4)} milliseconds.`) const result = { data: encryptedContent, timeTaken, encryptedFilePath } if (callback && typeof callback === 'function') { callback(result) } return result } catch (error) { log(`Error during file encryption: ${error.message}`) throw error } } function decryptFile ( filePath, key, callback = null, outputFileName = null ) { try { const encryptedContent = fs.readFileSync(filePath, 'utf-8') const { data: decryptedText, timeTaken } = decrypt(encryptedContent, key) // Determine output path const decryptedFilePath = path.isAbsolute(outputFileName) ? outputFileName : path.join(path.dirname(filePath), outputFileName) // Use a better default filename based on the input if (!outputFileName) { const originalName = path.basename(filePath, '.encrypted') outputFileName = `${originalName}.decrypted` } // Write decrypted content fs.writeFileSync(decryptedFilePath, decryptedText, 'utf-8') log(`File decryption completed in ${timeTaken.toFixed(4)} milliseconds.`) const result = { data: decryptedText, timeTaken, decryptedFilePath } if (callback && typeof callback === 'function') { callback(result) } return result } catch (error) { log(`Error during file decryption: ${error.message}`) throw error } } function encryptBinary(data, key, callback = null) { if (!key || typeof key !== 'string') { throw new Error('A valid key is required for encryption.') } // Convert binary data to Base64 string for encryption const base64Data = Buffer.from(data).toString('base64') const { data: encryptedBase64, timeTaken } = encrypt(base64Data, key) // Add format identifier to track the original format const encryptedWithFormat = `BINARY:${encryptedBase64}` const result = { data: encryptedWithFormat, timeTaken } if (callback && typeof callback === 'function') { callback(result) } return result } function decryptBinary(encryptedData, key, callback = null) { if (!key || typeof key !== 'string') { throw new Error('A valid key is required for decryption.') } if (!encryptedData.startsWith('BINARY:')) { throw new Error('Not a valid binary encrypted content') } // Remove format identifier const encryptedBase64 = encryptedData.substring(7) const { data: decryptedBase64, timeTaken } = decrypt(encryptedBase64, key) // Convert back from Base64 to binary const binaryData = Buffer.from(decryptedBase64, 'base64') const result = { data: binaryData, timeTaken } if (callback && typeof callback === 'function') { callback(result) } return result } function createEncryptStream(key) { if (!key || typeof key !== 'string') { throw new Error('A valid key is required for encryption.') } let buffer = '' return new Transform({ transform(chunk, encoding, callback) { try { const text = buffer + chunk.toString() // Process in chunks of reasonable size const chunkSize = 1024 * 10 // 10KB chunks const chunks = [] for (let i = 0; i < Math.floor(text.length / chunkSize); i++) { const startIndex = i * chunkSize const endIndex = (i + 1) * chunkSize const textChunk = text.substring(startIndex, endIndex) const encryptedChunk = encryptionAlgorithm( textChunk, key, settings.obfuscationLevel, settings.preserveWhitespace ) chunks.push(encryptedChunk) } // Keep remainder for next chunk buffer = text.substring(Math.floor(text.length / chunkSize) * chunkSize) callback(null, chunks.join('')) } catch (error) { callback(error) } }, flush(callback) { try { if (buffer.length > 0) { const encryptedChunk = encryptionAlgorithm( buffer, key, settings.obfuscationLevel, settings.preserveWhitespace ) callback(null, encryptedChunk) } else { callback(null) } } catch (error) { callback(error) } } }) } function createDecryptStream(key) { if (!key || typeof key !== 'string') { throw new Error('A valid key is required for decryption.') } let buffer = '' return new Transform({ transform(chunk, encoding, callback) { try { const text = buffer + chunk.toString() // Process in chunks of reasonable size const chunkSize = 1024 * 10 // 10KB chunks const chunks = [] for (let i = 0; i < Math.floor(text.length / chunkSize); i++) { const startIndex = i * chunkSize const endIndex = (i + 1) * chunkSize const textChunk = text.substring(startIndex, endIndex) const decryptedChunk = decryptionAlgorithm( textChunk, key, settings.obfuscationLevel, settings.preserveWhitespace ) chunks.push(decryptedChunk) } // Keep remainder for next chunk buffer = text.substring(Math.floor(text.length / chunkSize) * chunkSize) callback(null, chunks.join('')) } catch (error) { callback(error) } }, flush(callback) { try { if (buffer.length > 0) { const decryptedChunk = decryptionAlgorithm( buffer, key, settings.obfuscationLevel, settings.preserveWhitespace ) callback(null, decryptedChunk) } else { callback(null) } } catch (error) { callback(error) } } }) } async function encryptLargeFile(inputPath, outputPath, key) { if (!fs.existsSync(inputPath)) { throw new Error(`File not found: ${inputPath}`) } const start = performance.now() await pipeline( createReadStream(inputPath), createEncryptStream(key), createWriteStream(outputPath) ) const end = performance.now() const timeTaken = end - start log(`Large file encryption completed in ${timeTaken.toFixed(4)} milliseconds.`) return { inputPath, outputPath, timeTaken } } async function decryptLargeFile(inputPath, outputPath, key) { if (!fs.existsSync(inputPath)) { throw new Error(`File not found: ${inputPath}`) } const start = performance.now() await pipeline( createReadStream(inputPath), createDecryptStream(key), createWriteStream(outputPath) ) const end = performance.now() const timeTaken = end - start log(`Large file decryption completed in ${timeTaken.toFixed(4)} milliseconds.`) return { inputPath, outputPath, timeTaken } } return { encrypt, decrypt, encryptFile, decryptFile, encryptBinary, decryptBinary, createEncryptStream, createDecryptStream, encryptLargeFile, decryptLargeFile } } export default Cryptia