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