enc-x 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +43 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -248,6 +248,23 @@ function createProgressBar(label) {
|
|
|
248
248
|
};
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// src/utils/password.ts
|
|
252
|
+
function validatePassword(password) {
|
|
253
|
+
const errors = [];
|
|
254
|
+
if (password.length < 6) {
|
|
255
|
+
errors.push("Password must be at least 6 characters");
|
|
256
|
+
}
|
|
257
|
+
if (!/[a-zA-Z]/.test(password)) {
|
|
258
|
+
errors.push("Password must contain at least one letter");
|
|
259
|
+
}
|
|
260
|
+
if (!/[0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/.test(password)) {
|
|
261
|
+
errors.push(
|
|
262
|
+
"Password must contain at least one number or special character"
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
return { valid: errors.length === 0, errors };
|
|
266
|
+
}
|
|
267
|
+
|
|
251
268
|
// src/cli/commands/encrypt.ts
|
|
252
269
|
async function runEncrypt(filePath, opts) {
|
|
253
270
|
const inputPath = path3__default.default.resolve(filePath);
|
|
@@ -255,10 +272,11 @@ async function runEncrypt(filePath, opts) {
|
|
|
255
272
|
ui.error(`File not found: ${filePath}`);
|
|
256
273
|
process.exit(1);
|
|
257
274
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
);
|
|
275
|
+
const { valid, errors } = validatePassword(opts.key);
|
|
276
|
+
if (!valid) {
|
|
277
|
+
ui.error("Weak password:");
|
|
278
|
+
errors.forEach((e) => ui.error(` \u2022 ${e}`));
|
|
279
|
+
process.exit(1);
|
|
262
280
|
}
|
|
263
281
|
const outputPath = opts.output ?? inputPath + FILE_EXTENSION;
|
|
264
282
|
ui.info(`Encrypting ${chalk__default.default.bold(path3__default.default.basename(inputPath))}`);
|
|
@@ -334,11 +352,6 @@ async function runDecrypt(filePath, opts) {
|
|
|
334
352
|
ui.error(`File not found: ${filePath}`);
|
|
335
353
|
process.exit(1);
|
|
336
354
|
}
|
|
337
|
-
if (!inputPath.endsWith(FILE_EXTENSION)) {
|
|
338
|
-
ui.warn(
|
|
339
|
-
`File does not have a ${FILE_EXTENSION} extension \u2014 it may not be an encrypted file.`
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
355
|
ui.info(`Decrypting ${chalk__default.default.bold(path3__default.default.basename(inputPath))}`);
|
|
343
356
|
const bar = createProgressBar("Decrypting");
|
|
344
357
|
let barStarted = false;
|
|
@@ -373,13 +386,32 @@ async function runDecrypt(filePath, opts) {
|
|
|
373
386
|
process.exit(1);
|
|
374
387
|
}
|
|
375
388
|
}
|
|
389
|
+
async function isEncryptedFile(filePath) {
|
|
390
|
+
let fd = null;
|
|
391
|
+
try {
|
|
392
|
+
fd = await fs.promises.open(filePath, "r");
|
|
393
|
+
const buf = Buffer.alloc(4);
|
|
394
|
+
const { bytesRead } = await fd.read(buf, 0, 4, 0);
|
|
395
|
+
if (bytesRead < 4) return false;
|
|
396
|
+
return buf.equals(MAGIC);
|
|
397
|
+
} catch {
|
|
398
|
+
return false;
|
|
399
|
+
} finally {
|
|
400
|
+
await fd?.close();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
376
403
|
|
|
377
404
|
// src/index.ts
|
|
378
405
|
var program = new commander.Command();
|
|
379
406
|
program.name("enc-x").description(
|
|
380
407
|
chalk__default.default.cyan("enc-x") + " \u2014 secure AES-256-GCM file encryption & decryption"
|
|
381
|
-
).version("
|
|
382
|
-
if (
|
|
408
|
+
).version("2.0.0", "-v, --version").argument("<file>", "file to encrypt or decrypt (auto-detected from content)").requiredOption("-k, --key <password>", "password").option("-o, --output <path>", "custom output path").action(async (file, opts) => {
|
|
409
|
+
if (!fs.existsSync(file)) {
|
|
410
|
+
ui.error(`File not found: ${file}`);
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
const encrypted = await isEncryptedFile(file);
|
|
414
|
+
if (encrypted) {
|
|
383
415
|
await runDecrypt(file, opts);
|
|
384
416
|
} else {
|
|
385
417
|
await runEncrypt(file, opts);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/crypto/constants.ts","../src/crypto/keys.ts","../src/crypto/header.ts","../src/utils/mime.ts","../src/utils/progress.ts","../src/crypto/encrypt.ts","../src/utils/format.ts","../src/cli/ui.ts","../src/cli/commands/encrypt.ts","../src/crypto/decrypt.ts","../src/cli/commands/decrypt.ts","../src/index.ts"],"names":["randomBytes","pbkdf2","path","Transform","fsp","createWriteStream","createCipheriv","createReadStream","pipeline","chalk","cliProgress","existsSync","createDecipheriv","Command"],"mappings":"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","file":"index.js","sourcesContent":["// Encryption constants\nexport const ALGORITHM = \"aes-256-gcm\" as const;\nexport const KEY_LENGTH = 32; // 256 bits\nexport const IV_LENGTH = 12; // 96 bits — recommended for GCM\nexport const SALT_LENGTH = 32; // 256 bits\nexport const AUTH_TAG_LENGTH = 16; // 128 bits — GCM auth tag\nexport const PBKDF2_ITERATIONS = 100_000;\nexport const PBKDF2_DIGEST = \"sha512\" as const;\nexport const FILE_EXTENSION = \".enc-x\";\n\n// Header layout (binary, written at start of .enc-x file):\n//\n// [4 bytes] magic number 0x454E4358 (\"ENCX\")\n// [1 byte] version 0x01\n// [32 bytes] salt\n// [12 bytes] IV\n// [16 bytes] GCM auth tag (written after encryption, patched back)\n// [2 bytes] metadata JSON length (uint16 BE)\n// [N bytes] metadata JSON (UTF-8)\n//\nexport const MAGIC = Buffer.from([0x45, 0x4e, 0x43, 0x58]); // \"ENCX\"\nexport const VERSION = 0x01;\n\nexport const HEADER_FIXED_SIZE =\n 4 + // magic\n 1 + // version\n SALT_LENGTH +\n IV_LENGTH +\n AUTH_TAG_LENGTH +\n 2; // metadata length prefix\n","import { randomBytes, pbkdf2 } from \"node:crypto\";\nimport {\n KEY_LENGTH,\n SALT_LENGTH,\n PBKDF2_ITERATIONS,\n PBKDF2_DIGEST,\n} from \"@/crypto/constants\";\n\n/**\n * Generate a cryptographically secure random salt.\n */\nexport function generateSalt(): Buffer {\n return randomBytes(SALT_LENGTH);\n}\n\n/**\n * Generate a cryptographically secure random IV.\n */\nexport function generateIV(length: number): Buffer {\n return randomBytes(length);\n}\n\n/**\n * Derive a 256-bit key from a password + salt using PBKDF2-SHA512.\n */\nexport function deriveKey(password: string, salt: Buffer): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n pbkdf2(\n password,\n salt,\n PBKDF2_ITERATIONS,\n KEY_LENGTH,\n PBKDF2_DIGEST,\n (err, key) => {\n if (err) reject(err);\n else resolve(key);\n },\n );\n });\n}\n","import {\n MAGIC,\n VERSION,\n SALT_LENGTH,\n IV_LENGTH,\n AUTH_TAG_LENGTH,\n} from \"@/crypto/constants\";\n\nexport interface FileMetadata {\n name: string;\n mime: string;\n size?: number;\n}\n\nexport interface EncxHeader {\n salt: Buffer;\n iv: Buffer;\n authTag: Buffer;\n metadata: FileMetadata;\n /** Total byte length of the header (fixed + variable JSON) */\n totalSize: number;\n}\n\n/**\n * Serialize the header into a Buffer.\n * Auth tag is written as 16 zero bytes and must be patched after encryption.\n */\nexport function serializeHeader(\n salt: Buffer,\n iv: Buffer,\n metadata: FileMetadata,\n authTag: Buffer = Buffer.alloc(AUTH_TAG_LENGTH),\n): Buffer {\n const metaJson = Buffer.from(JSON.stringify(metadata), \"utf8\");\n const metaLen = Buffer.alloc(2);\n metaLen.writeUInt16BE(metaJson.length, 0);\n\n return Buffer.concat([\n MAGIC,\n Buffer.from([VERSION]),\n salt,\n iv,\n authTag,\n metaLen,\n metaJson,\n ]);\n}\n\n/**\n * Parse a header from the start of an .enc-x file buffer.\n * Throws if the magic number or version is invalid.\n */\nexport function parseHeader(buf: Buffer): EncxHeader {\n let offset = 0;\n\n // Magic\n const magic = buf.subarray(offset, offset + 4);\n offset += 4;\n if (!magic.equals(MAGIC)) {\n throw new Error(\"Not a valid .enc-x file (bad magic number)\");\n }\n\n // Version\n const version = buf[offset++];\n if (version !== VERSION) {\n throw new Error(`Unsupported .enc-x version: ${version}`);\n }\n\n // Salt\n const salt = Buffer.from(buf.subarray(offset, offset + SALT_LENGTH));\n offset += SALT_LENGTH;\n\n // IV\n const iv = Buffer.from(buf.subarray(offset, offset + IV_LENGTH));\n offset += IV_LENGTH;\n\n // Auth tag\n const authTag = Buffer.from(buf.subarray(offset, offset + AUTH_TAG_LENGTH));\n offset += AUTH_TAG_LENGTH;\n\n // Metadata length\n const metaLen = buf.readUInt16BE(offset);\n offset += 2;\n\n // Metadata JSON\n const metaJson = buf.subarray(offset, offset + metaLen);\n offset += metaLen;\n\n let metadata: FileMetadata;\n try {\n metadata = JSON.parse(metaJson.toString(\"utf8\")) as FileMetadata;\n } catch {\n throw new Error(\"Corrupted .enc-x file (invalid metadata)\");\n }\n\n return { salt, iv, authTag, metadata, totalSize: offset };\n}\n\n/**\n * Patch the auth tag into an already-written header in a file.\n * The auth tag sits at a fixed offset after magic(4) + version(1) + salt(32) + iv(12).\n */\nexport const AUTH_TAG_OFFSET = 4 + 1 + SALT_LENGTH + IV_LENGTH;\n","import path from \"node:path\";\n\n// Lightweight extension → MIME map (no external deps)\nconst MIME_MAP: Record<string, string> = {\n // Video\n mp4: \"video/mp4\",\n mkv: \"video/x-matroska\",\n avi: \"video/x-msvideo\",\n mov: \"video/quicktime\",\n webm: \"video/webm\",\n // Audio\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n flac: \"audio/flac\",\n aac: \"audio/aac\",\n ogg: \"audio/ogg\",\n // Images\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n svg: \"image/svg+xml\",\n // Documents\n pdf: \"application/pdf\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ppt: \"application/vnd.ms-powerpoint\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n // Archives\n zip: \"application/zip\",\n tar: \"application/x-tar\",\n gz: \"application/gzip\",\n \"7z\": \"application/x-7z-compressed\",\n rar: \"application/vnd.rar\",\n // Text / code\n txt: \"text/plain\",\n json: \"application/json\",\n xml: \"application/xml\",\n html: \"text/html\",\n css: \"text/css\",\n js: \"application/javascript\",\n ts: \"application/typescript\",\n // Executables / binaries\n exe: \"application/x-msdownload\",\n dmg: \"application/x-apple-diskimage\",\n iso: \"application/x-iso9660-image\",\n};\n\nexport function getMimeType(filePath: string): string {\n const ext = path.extname(filePath).replace(\".\", \"\").toLowerCase();\n return MIME_MAP[ext] ?? \"application/octet-stream\";\n}\n","import { Transform, type TransformCallback } from \"node:stream\";\n\n/**\n * A passthrough Transform stream that tracks bytes flowing through it\n * and calls onProgress(bytesProcessed, total).\n */\nexport function createProgressStream(\n total: number,\n onProgress: (bytesProcessed: number, total: number) => void,\n): Transform {\n let processed = 0;\n\n return new Transform({\n transform(\n chunk: Buffer,\n _encoding: BufferEncoding,\n callback: TransformCallback,\n ) {\n processed += chunk.length;\n onProgress(processed, total);\n callback(null, chunk);\n },\n });\n}\n","import { createCipheriv } from \"node:crypto\";\nimport { createReadStream, createWriteStream, promises as fsp } from \"node:fs\";\nimport { pipeline } from \"node:stream/promises\";\nimport { Transform } from \"node:stream\";\nimport path from \"node:path\";\nimport {\n ALGORITHM,\n IV_LENGTH,\n FILE_EXTENSION,\n AUTH_TAG_LENGTH,\n} from \"@/crypto/constants\";\nimport { generateSalt, generateIV, deriveKey } from \"@/crypto/keys\";\nimport {\n serializeHeader,\n AUTH_TAG_OFFSET,\n type FileMetadata,\n} from \"@/crypto/header\";\nimport { getMimeType } from \"@/utils/mime\";\nimport { createProgressStream } from \"@/utils/progress\";\n\nexport interface EncryptOptions {\n inputPath: string;\n password: string;\n outputPath?: string;\n onProgress?: (bytesProcessed: number, total: number) => void;\n}\n\nexport interface EncryptResult {\n outputPath: string;\n originalSize: number;\n}\n\nexport async function encryptFile(\n opts: EncryptOptions,\n): Promise<EncryptResult> {\n const { inputPath, password } = opts;\n\n // Stat the input file\n const stat = await fsp.stat(inputPath);\n const originalSize = stat.size;\n const originalName = path.basename(inputPath);\n const mime = getMimeType(inputPath);\n\n // Derive key\n const salt = generateSalt();\n const iv = generateIV(IV_LENGTH);\n const key = await deriveKey(password, salt);\n\n // Build metadata\n const metadata: FileMetadata = {\n name: originalName,\n mime,\n size: originalSize,\n };\n\n // Write placeholder header (auth tag = 16 zero bytes, patched later)\n const headerBuf = serializeHeader(salt, iv, metadata);\n\n // Output path\n const outputPath = opts.outputPath ?? inputPath + FILE_EXTENSION;\n const writeStream = createWriteStream(outputPath);\n\n // Write header first\n await new Promise<void>((resolve, reject) => {\n writeStream.write(headerBuf, (err) => (err ? reject(err) : resolve()));\n });\n\n // Create cipher\n const cipher = createCipheriv(ALGORITHM, key, iv);\n\n // Stream: input → [progress] → cipher → output\n const readStream = createReadStream(inputPath);\n const progressStream = opts.onProgress\n ? createProgressStream(originalSize, opts.onProgress)\n : null;\n\n const source = progressStream ? readStream.pipe(progressStream) : readStream;\n\n await pipeline(source as NodeJS.ReadableStream, cipher, writeStream);\n\n // Retrieve and patch auth tag back into the file header\n const authTag = cipher.getAuthTag();\n const fd = await fsp.open(outputPath, \"r+\");\n try {\n await fd.write(authTag, 0, AUTH_TAG_LENGTH, AUTH_TAG_OFFSET);\n } finally {\n await fd.close();\n }\n\n return { outputPath, originalSize };\n}\n","/**\n * Format bytes into a human-readable string.\n */\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n const value = bytes / Math.pow(1024, i);\n return `${value.toFixed(i === 0 ? 0 : 2)} ${units[i]}`;\n}\n\n/**\n * Format milliseconds into a human-readable duration.\n */\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n const s = ms / 1000;\n if (s < 60) return `${s.toFixed(1)}s`;\n const m = Math.floor(s / 60);\n const rem = (s % 60).toFixed(0).padStart(2, \"0\");\n return `${m}m ${rem}s`;\n}\n","import chalk from \"chalk\";\nimport cliProgress from \"cli-progress\";\nimport { formatBytes } from \"@/utils/format\";\n\nexport const ui = {\n info: (msg: string) => console.log(chalk.cyan(\"ℹ\"), msg),\n success: (msg: string) => console.log(chalk.green(\"✔\"), msg),\n error: (msg: string) => console.error(chalk.red(\"✖\"), chalk.red(msg)),\n warn: (msg: string) => console.warn(chalk.yellow(\"⚠\"), chalk.yellow(msg)),\n dim: (msg: string) => console.log(chalk.dim(msg)),\n};\n\nexport function createProgressBar(label: string) {\n const bar = new cliProgress.SingleBar(\n {\n format:\n `${chalk.cyan(label)} ${chalk.cyan(\"{bar}\")} {percentage}%` +\n ` | {value_fmt} / {total_fmt} | ETA: {eta}s`,\n barCompleteChar: \"█\",\n barIncompleteChar: \"░\",\n hideCursor: true,\n clearOnComplete: false,\n stopOnComplete: true,\n },\n cliProgress.Presets.shades_classic,\n );\n\n return {\n start(total: number) {\n bar.start(total, 0, {\n value_fmt: formatBytes(0),\n total_fmt: formatBytes(total),\n });\n },\n update(value: number, total: number) {\n bar.update(value, {\n value_fmt: formatBytes(value),\n total_fmt: formatBytes(total),\n });\n },\n stop() {\n bar.stop();\n },\n };\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport chalk from \"chalk\";\nimport { encryptFile } from \"@/crypto/encrypt\";\nimport { FILE_EXTENSION } from \"@/crypto/constants\";\nimport { ui, createProgressBar } from \"@/cli/ui\";\nimport { formatBytes, formatDuration } from \"@/utils/format\";\n\nexport interface EncryptCommandOptions {\n key: string;\n output?: string;\n}\n\nexport async function runEncrypt(\n filePath: string,\n opts: EncryptCommandOptions,\n): Promise<void> {\n const inputPath = path.resolve(filePath);\n\n if (!existsSync(inputPath)) {\n ui.error(`File not found: ${filePath}`);\n process.exit(1);\n }\n\n if (inputPath.endsWith(FILE_EXTENSION)) {\n ui.warn(\n \"Input file already has .enc-x extension — it may already be encrypted.\",\n );\n }\n\n const outputPath = opts.output ?? inputPath + FILE_EXTENSION;\n\n ui.info(`Encrypting ${chalk.bold(path.basename(inputPath))}`);\n ui.dim(` Output : ${outputPath}`);\n ui.dim(` Cipher : AES-256-GCM | KDF: PBKDF2-SHA512 (100,000 iterations)`);\n\n const bar = createProgressBar(\"Encrypting\");\n let barStarted = false;\n const start = Date.now();\n\n try {\n const result = await encryptFile({\n inputPath,\n password: opts.key,\n outputPath,\n onProgress(processed, total) {\n if (!barStarted) {\n bar.start(total);\n barStarted = true;\n }\n bar.update(processed, total);\n },\n });\n\n if (barStarted) bar.stop();\n\n const elapsed = Date.now() - start;\n ui.success(\n `Done in ${formatDuration(elapsed)} — ` +\n `${chalk.bold(path.basename(result.outputPath))} ` +\n `(${formatBytes(result.originalSize)})`,\n );\n } catch (err: unknown) {\n if (barStarted) bar.stop();\n const msg = err instanceof Error ? err.message : String(err);\n ui.error(`Encryption failed: ${msg}`);\n process.exit(1);\n }\n}\n","import { createDecipheriv } from \"node:crypto\";\nimport { createReadStream, createWriteStream, promises as fsp } from \"node:fs\";\nimport { pipeline } from \"node:stream/promises\";\nimport { PassThrough } from \"node:stream\";\nimport path from \"node:path\";\nimport { ALGORITHM } from \"@/crypto/constants\";\nimport { deriveKey } from \"@/crypto/keys\";\nimport { parseHeader } from \"@/crypto/header\";\nimport { createProgressStream } from \"@/utils/progress\";\n\nexport interface DecryptOptions {\n inputPath: string;\n password: string;\n outputPath?: string;\n onProgress?: (bytesProcessed: number, total: number) => void;\n}\n\nexport interface DecryptResult {\n outputPath: string;\n originalName: string;\n}\n\n// How many bytes to read upfront to parse the header\n// Max header = fixed(67) + 2(len) + 65535(max JSON) — in practice ~200 bytes\nconst HEADER_READ_LIMIT = 4096;\n\nexport async function decryptFile(\n opts: DecryptOptions,\n): Promise<DecryptResult> {\n const { inputPath, password } = opts;\n\n const stat = await fsp.stat(inputPath);\n const totalSize = stat.size;\n\n // Read enough bytes to parse the header\n const fd = await fsp.open(inputPath, \"r\");\n const headerBuf = Buffer.alloc(HEADER_READ_LIMIT);\n const { bytesRead } = await fd.read(headerBuf, 0, HEADER_READ_LIMIT, 0);\n await fd.close();\n\n const header = parseHeader(headerBuf.subarray(0, bytesRead));\n const { salt, iv, authTag, metadata, totalSize: headerSize } = header;\n\n // Derive key from password + salt stored in file\n const key = await deriveKey(password, salt);\n\n // Determine output path\n const outputDir = path.dirname(inputPath);\n const outputPath = opts.outputPath ?? path.join(outputDir, metadata.name);\n\n // Create decipher and set auth tag\n const decipher = createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(authTag);\n\n // Stream ciphertext (skip header bytes) → decipher → output file\n const ciphertextSize = totalSize - headerSize;\n const readStream = createReadStream(inputPath, { start: headerSize });\n\n const progressStream = opts.onProgress\n ? createProgressStream(ciphertextSize, opts.onProgress)\n : null;\n\n const writeStream = createWriteStream(outputPath);\n\n const source = progressStream ? readStream.pipe(progressStream) : readStream;\n\n try {\n await pipeline(source as NodeJS.ReadableStream, decipher, writeStream);\n } catch (err: unknown) {\n // Clean up partial output on auth failure\n await fsp.unlink(outputPath).catch(() => {});\n\n const msg = err instanceof Error ? err.message : String(err);\n if (\n msg.includes(\"Unsupported state\") ||\n msg.includes(\"bad decrypt\") ||\n msg.includes(\"auth\") ||\n msg.includes(\"ERR_CRYPTO\")\n ) {\n throw new Error(\"Invalid password or corrupted file\");\n }\n throw err;\n }\n\n return { outputPath, originalName: metadata.name };\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport chalk from \"chalk\";\nimport { decryptFile } from \"@/crypto/decrypt\";\nimport { FILE_EXTENSION } from \"@/crypto/constants\";\nimport { ui, createProgressBar } from \"@/cli/ui\";\nimport { formatDuration } from \"@/utils/format\";\n\nexport interface DecryptCommandOptions {\n key: string;\n output?: string;\n}\n\nexport async function runDecrypt(\n filePath: string,\n opts: DecryptCommandOptions,\n): Promise<void> {\n const inputPath = path.resolve(filePath);\n\n if (!existsSync(inputPath)) {\n ui.error(`File not found: ${filePath}`);\n process.exit(1);\n }\n\n if (!inputPath.endsWith(FILE_EXTENSION)) {\n ui.warn(\n `File does not have a ${FILE_EXTENSION} extension — it may not be an encrypted file.`,\n );\n }\n\n ui.info(`Decrypting ${chalk.bold(path.basename(inputPath))}`);\n\n const bar = createProgressBar(\"Decrypting\");\n let barStarted = false;\n const start = Date.now();\n\n try {\n const result = await decryptFile({\n inputPath,\n password: opts.key,\n outputPath: opts.output,\n onProgress(processed, total) {\n if (!barStarted) {\n bar.start(total);\n barStarted = true;\n }\n bar.update(processed, total);\n },\n });\n\n if (barStarted) bar.stop();\n\n const elapsed = Date.now() - start;\n ui.success(\n `Done in ${formatDuration(elapsed)} — ` +\n `restored ${chalk.bold(result.originalName)}`,\n );\n ui.dim(` Output : ${result.outputPath}`);\n } catch (err: unknown) {\n if (barStarted) bar.stop();\n const msg = err instanceof Error ? err.message : String(err);\n\n if (msg === \"Invalid password or corrupted file\") {\n ui.error(\"Invalid password or corrupted file\");\n } else {\n ui.error(`Decryption failed: ${msg}`);\n }\n process.exit(1);\n }\n}\n","import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { runEncrypt } from \"@/cli/commands/encrypt\";\nimport { runDecrypt } from \"@/cli/commands/decrypt\";\nimport { FILE_EXTENSION } from \"@/crypto/constants\";\n\nconst program = new Command();\n\nprogram\n .name(\"enc-x\")\n .description(\n chalk.cyan(\"enc-x\") + \" — secure AES-256-GCM file encryption & decryption\",\n )\n .version(\"1.1.1\", \"-v, --version\")\n .argument(\"<file>\", \"file to encrypt or decrypt (auto-detected by extension)\")\n .requiredOption(\"-k, --key <password>\", \"password\")\n .option(\"-o, --output <path>\", \"custom output path\")\n .action(async (file: string, opts: { key: string; output?: string }) => {\n if (file.endsWith(FILE_EXTENSION)) {\n await runDecrypt(file, opts);\n } else {\n await runEncrypt(file, opts);\n }\n });\n\nprogram.parse(process.argv);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/crypto/constants.ts","../src/crypto/keys.ts","../src/crypto/header.ts","../src/utils/mime.ts","../src/utils/progress.ts","../src/crypto/encrypt.ts","../src/utils/format.ts","../src/cli/ui.ts","../src/utils/password.ts","../src/cli/commands/encrypt.ts","../src/crypto/decrypt.ts","../src/cli/commands/decrypt.ts","../src/utils/detect.ts","../src/index.ts"],"names":["randomBytes","pbkdf2","path","Transform","fsp","createWriteStream","createCipheriv","createReadStream","pipeline","chalk","cliProgress","existsSync","createDecipheriv","Command"],"mappings":"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","file":"index.js","sourcesContent":["// Encryption constants\nexport const ALGORITHM = \"aes-256-gcm\" as const;\nexport const KEY_LENGTH = 32; // 256 bits\nexport const IV_LENGTH = 12; // 96 bits — recommended for GCM\nexport const SALT_LENGTH = 32; // 256 bits\nexport const AUTH_TAG_LENGTH = 16; // 128 bits — GCM auth tag\nexport const PBKDF2_ITERATIONS = 100_000;\nexport const PBKDF2_DIGEST = \"sha512\" as const;\nexport const FILE_EXTENSION = \".enc-x\";\n\n// Header layout (binary, written at start of .enc-x file):\n//\n// [4 bytes] magic number 0x454E4358 (\"ENCX\")\n// [1 byte] version 0x01\n// [32 bytes] salt\n// [12 bytes] IV\n// [16 bytes] GCM auth tag (written after encryption, patched back)\n// [2 bytes] metadata JSON length (uint16 BE)\n// [N bytes] metadata JSON (UTF-8)\n//\nexport const MAGIC = Buffer.from([0x45, 0x4e, 0x43, 0x58]); // \"ENCX\"\nexport const VERSION = 0x01;\n\nexport const HEADER_FIXED_SIZE =\n 4 + // magic\n 1 + // version\n SALT_LENGTH +\n IV_LENGTH +\n AUTH_TAG_LENGTH +\n 2; // metadata length prefix\n","import { randomBytes, pbkdf2 } from \"node:crypto\";\nimport {\n KEY_LENGTH,\n SALT_LENGTH,\n PBKDF2_ITERATIONS,\n PBKDF2_DIGEST,\n} from \"@/crypto/constants\";\n\n/**\n * Generate a cryptographically secure random salt.\n */\nexport function generateSalt(): Buffer {\n return randomBytes(SALT_LENGTH);\n}\n\n/**\n * Generate a cryptographically secure random IV.\n */\nexport function generateIV(length: number): Buffer {\n return randomBytes(length);\n}\n\n/**\n * Derive a 256-bit key from a password + salt using PBKDF2-SHA512.\n */\nexport function deriveKey(password: string, salt: Buffer): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n pbkdf2(\n password,\n salt,\n PBKDF2_ITERATIONS,\n KEY_LENGTH,\n PBKDF2_DIGEST,\n (err, key) => {\n if (err) reject(err);\n else resolve(key);\n },\n );\n });\n}\n","import {\n MAGIC,\n VERSION,\n SALT_LENGTH,\n IV_LENGTH,\n AUTH_TAG_LENGTH,\n} from \"@/crypto/constants\";\n\nexport interface FileMetadata {\n name: string;\n mime: string;\n size?: number;\n}\n\nexport interface EncxHeader {\n salt: Buffer;\n iv: Buffer;\n authTag: Buffer;\n metadata: FileMetadata;\n /** Total byte length of the header (fixed + variable JSON) */\n totalSize: number;\n}\n\n/**\n * Serialize the header into a Buffer.\n * Auth tag is written as 16 zero bytes and must be patched after encryption.\n */\nexport function serializeHeader(\n salt: Buffer,\n iv: Buffer,\n metadata: FileMetadata,\n authTag: Buffer = Buffer.alloc(AUTH_TAG_LENGTH),\n): Buffer {\n const metaJson = Buffer.from(JSON.stringify(metadata), \"utf8\");\n const metaLen = Buffer.alloc(2);\n metaLen.writeUInt16BE(metaJson.length, 0);\n\n return Buffer.concat([\n MAGIC,\n Buffer.from([VERSION]),\n salt,\n iv,\n authTag,\n metaLen,\n metaJson,\n ]);\n}\n\n/**\n * Parse a header from the start of an .enc-x file buffer.\n * Throws if the magic number or version is invalid.\n */\nexport function parseHeader(buf: Buffer): EncxHeader {\n let offset = 0;\n\n // Magic\n const magic = buf.subarray(offset, offset + 4);\n offset += 4;\n if (!magic.equals(MAGIC)) {\n throw new Error(\"Not a valid .enc-x file (bad magic number)\");\n }\n\n // Version\n const version = buf[offset++];\n if (version !== VERSION) {\n throw new Error(`Unsupported .enc-x version: ${version}`);\n }\n\n // Salt\n const salt = Buffer.from(buf.subarray(offset, offset + SALT_LENGTH));\n offset += SALT_LENGTH;\n\n // IV\n const iv = Buffer.from(buf.subarray(offset, offset + IV_LENGTH));\n offset += IV_LENGTH;\n\n // Auth tag\n const authTag = Buffer.from(buf.subarray(offset, offset + AUTH_TAG_LENGTH));\n offset += AUTH_TAG_LENGTH;\n\n // Metadata length\n const metaLen = buf.readUInt16BE(offset);\n offset += 2;\n\n // Metadata JSON\n const metaJson = buf.subarray(offset, offset + metaLen);\n offset += metaLen;\n\n let metadata: FileMetadata;\n try {\n metadata = JSON.parse(metaJson.toString(\"utf8\")) as FileMetadata;\n } catch {\n throw new Error(\"Corrupted .enc-x file (invalid metadata)\");\n }\n\n return { salt, iv, authTag, metadata, totalSize: offset };\n}\n\n/**\n * Patch the auth tag into an already-written header in a file.\n * The auth tag sits at a fixed offset after magic(4) + version(1) + salt(32) + iv(12).\n */\nexport const AUTH_TAG_OFFSET = 4 + 1 + SALT_LENGTH + IV_LENGTH;\n","import path from \"node:path\";\n\n// Lightweight extension → MIME map (no external deps)\nconst MIME_MAP: Record<string, string> = {\n // Video\n mp4: \"video/mp4\",\n mkv: \"video/x-matroska\",\n avi: \"video/x-msvideo\",\n mov: \"video/quicktime\",\n webm: \"video/webm\",\n // Audio\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n flac: \"audio/flac\",\n aac: \"audio/aac\",\n ogg: \"audio/ogg\",\n // Images\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n svg: \"image/svg+xml\",\n // Documents\n pdf: \"application/pdf\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ppt: \"application/vnd.ms-powerpoint\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n // Archives\n zip: \"application/zip\",\n tar: \"application/x-tar\",\n gz: \"application/gzip\",\n \"7z\": \"application/x-7z-compressed\",\n rar: \"application/vnd.rar\",\n // Text / code\n txt: \"text/plain\",\n json: \"application/json\",\n xml: \"application/xml\",\n html: \"text/html\",\n css: \"text/css\",\n js: \"application/javascript\",\n ts: \"application/typescript\",\n // Executables / binaries\n exe: \"application/x-msdownload\",\n dmg: \"application/x-apple-diskimage\",\n iso: \"application/x-iso9660-image\",\n};\n\nexport function getMimeType(filePath: string): string {\n const ext = path.extname(filePath).replace(\".\", \"\").toLowerCase();\n return MIME_MAP[ext] ?? \"application/octet-stream\";\n}\n","import { Transform, type TransformCallback } from \"node:stream\";\n\n/**\n * A passthrough Transform stream that tracks bytes flowing through it\n * and calls onProgress(bytesProcessed, total).\n */\nexport function createProgressStream(\n total: number,\n onProgress: (bytesProcessed: number, total: number) => void,\n): Transform {\n let processed = 0;\n\n return new Transform({\n transform(\n chunk: Buffer,\n _encoding: BufferEncoding,\n callback: TransformCallback,\n ) {\n processed += chunk.length;\n onProgress(processed, total);\n callback(null, chunk);\n },\n });\n}\n","import { createCipheriv } from \"node:crypto\";\nimport { createReadStream, createWriteStream, promises as fsp } from \"node:fs\";\nimport { pipeline } from \"node:stream/promises\";\nimport { Transform } from \"node:stream\";\nimport path from \"node:path\";\nimport {\n ALGORITHM,\n IV_LENGTH,\n FILE_EXTENSION,\n AUTH_TAG_LENGTH,\n} from \"@/crypto/constants\";\nimport { generateSalt, generateIV, deriveKey } from \"@/crypto/keys\";\nimport {\n serializeHeader,\n AUTH_TAG_OFFSET,\n type FileMetadata,\n} from \"@/crypto/header\";\nimport { getMimeType } from \"@/utils/mime\";\nimport { createProgressStream } from \"@/utils/progress\";\n\nexport interface EncryptOptions {\n inputPath: string;\n password: string;\n outputPath?: string;\n onProgress?: (bytesProcessed: number, total: number) => void;\n}\n\nexport interface EncryptResult {\n outputPath: string;\n originalSize: number;\n}\n\nexport async function encryptFile(\n opts: EncryptOptions,\n): Promise<EncryptResult> {\n const { inputPath, password } = opts;\n\n // Stat the input file\n const stat = await fsp.stat(inputPath);\n const originalSize = stat.size;\n const originalName = path.basename(inputPath);\n const mime = getMimeType(inputPath);\n\n // Derive key\n const salt = generateSalt();\n const iv = generateIV(IV_LENGTH);\n const key = await deriveKey(password, salt);\n\n // Build metadata\n const metadata: FileMetadata = {\n name: originalName,\n mime,\n size: originalSize,\n };\n\n // Write placeholder header (auth tag = 16 zero bytes, patched later)\n const headerBuf = serializeHeader(salt, iv, metadata);\n\n // Output path\n const outputPath = opts.outputPath ?? inputPath + FILE_EXTENSION;\n const writeStream = createWriteStream(outputPath);\n\n // Write header first\n await new Promise<void>((resolve, reject) => {\n writeStream.write(headerBuf, (err) => (err ? reject(err) : resolve()));\n });\n\n // Create cipher\n const cipher = createCipheriv(ALGORITHM, key, iv);\n\n // Stream: input → [progress] → cipher → output\n const readStream = createReadStream(inputPath);\n const progressStream = opts.onProgress\n ? createProgressStream(originalSize, opts.onProgress)\n : null;\n\n const source = progressStream ? readStream.pipe(progressStream) : readStream;\n\n await pipeline(source as NodeJS.ReadableStream, cipher, writeStream);\n\n // Retrieve and patch auth tag back into the file header\n const authTag = cipher.getAuthTag();\n const fd = await fsp.open(outputPath, \"r+\");\n try {\n await fd.write(authTag, 0, AUTH_TAG_LENGTH, AUTH_TAG_OFFSET);\n } finally {\n await fd.close();\n }\n\n return { outputPath, originalSize };\n}\n","/**\n * Format bytes into a human-readable string.\n */\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n const value = bytes / Math.pow(1024, i);\n return `${value.toFixed(i === 0 ? 0 : 2)} ${units[i]}`;\n}\n\n/**\n * Format milliseconds into a human-readable duration.\n */\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n const s = ms / 1000;\n if (s < 60) return `${s.toFixed(1)}s`;\n const m = Math.floor(s / 60);\n const rem = (s % 60).toFixed(0).padStart(2, \"0\");\n return `${m}m ${rem}s`;\n}\n","import chalk from \"chalk\";\nimport cliProgress from \"cli-progress\";\nimport { formatBytes } from \"@/utils/format\";\n\nexport const ui = {\n info: (msg: string) => console.log(chalk.cyan(\"ℹ\"), msg),\n success: (msg: string) => console.log(chalk.green(\"✔\"), msg),\n error: (msg: string) => console.error(chalk.red(\"✖\"), chalk.red(msg)),\n warn: (msg: string) => console.warn(chalk.yellow(\"⚠\"), chalk.yellow(msg)),\n dim: (msg: string) => console.log(chalk.dim(msg)),\n};\n\nexport function createProgressBar(label: string) {\n const bar = new cliProgress.SingleBar(\n {\n format:\n `${chalk.cyan(label)} ${chalk.cyan(\"{bar}\")} {percentage}%` +\n ` | {value_fmt} / {total_fmt} | ETA: {eta}s`,\n barCompleteChar: \"█\",\n barIncompleteChar: \"░\",\n hideCursor: true,\n clearOnComplete: false,\n stopOnComplete: true,\n },\n cliProgress.Presets.shades_classic,\n );\n\n return {\n start(total: number) {\n bar.start(total, 0, {\n value_fmt: formatBytes(0),\n total_fmt: formatBytes(total),\n });\n },\n update(value: number, total: number) {\n bar.update(value, {\n value_fmt: formatBytes(value),\n total_fmt: formatBytes(total),\n });\n },\n stop() {\n bar.stop();\n },\n };\n}\n","export interface PasswordValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validate password strength before encryption.\n * Rules:\n * - At least 6 characters\n * - At least 1 letter\n * - At least 1 number or special character\n */\nexport function validatePassword(password: string): PasswordValidationResult {\n const errors: string[] = [];\n\n if (password.length < 6) {\n errors.push(\"Password must be at least 6 characters\");\n }\n\n if (!/[a-zA-Z]/.test(password)) {\n errors.push(\"Password must contain at least one letter\");\n }\n\n if (!/[0-9!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?`~]/.test(password)) {\n errors.push(\n \"Password must contain at least one number or special character\",\n );\n }\n\n return { valid: errors.length === 0, errors };\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport chalk from \"chalk\";\nimport { encryptFile } from \"@/crypto/encrypt\";\nimport { FILE_EXTENSION } from \"@/crypto/constants\";\nimport { ui, createProgressBar } from \"@/cli/ui\";\nimport { formatBytes, formatDuration } from \"@/utils/format\";\nimport { validatePassword } from \"@/utils/password\";\n\nexport interface EncryptCommandOptions {\n key: string;\n output?: string;\n}\n\nexport async function runEncrypt(\n filePath: string,\n opts: EncryptCommandOptions,\n): Promise<void> {\n const inputPath = path.resolve(filePath);\n\n if (!existsSync(inputPath)) {\n ui.error(`File not found: ${filePath}`);\n process.exit(1);\n }\n\n // Validate password strength\n const { valid, errors } = validatePassword(opts.key);\n if (!valid) {\n ui.error(\"Weak password:\");\n errors.forEach((e) => ui.error(` • ${e}`));\n process.exit(1);\n }\n\n const outputPath = opts.output ?? inputPath + FILE_EXTENSION;\n\n ui.info(`Encrypting ${chalk.bold(path.basename(inputPath))}`);\n ui.dim(` Output : ${outputPath}`);\n ui.dim(` Cipher : AES-256-GCM | KDF: PBKDF2-SHA512 (100,000 iterations)`);\n\n const bar = createProgressBar(\"Encrypting\");\n let barStarted = false;\n const start = Date.now();\n\n try {\n const result = await encryptFile({\n inputPath,\n password: opts.key,\n outputPath,\n onProgress(processed, total) {\n if (!barStarted) {\n bar.start(total);\n barStarted = true;\n }\n bar.update(processed, total);\n },\n });\n\n if (barStarted) bar.stop();\n\n const elapsed = Date.now() - start;\n ui.success(\n `Done in ${formatDuration(elapsed)} — ` +\n `${chalk.bold(path.basename(result.outputPath))} ` +\n `(${formatBytes(result.originalSize)})`,\n );\n } catch (err: unknown) {\n if (barStarted) bar.stop();\n const msg = err instanceof Error ? err.message : String(err);\n ui.error(`Encryption failed: ${msg}`);\n process.exit(1);\n }\n}\n","import { createDecipheriv } from \"node:crypto\";\nimport { createReadStream, createWriteStream, promises as fsp } from \"node:fs\";\nimport { pipeline } from \"node:stream/promises\";\nimport { PassThrough } from \"node:stream\";\nimport path from \"node:path\";\nimport { ALGORITHM } from \"@/crypto/constants\";\nimport { deriveKey } from \"@/crypto/keys\";\nimport { parseHeader } from \"@/crypto/header\";\nimport { createProgressStream } from \"@/utils/progress\";\n\nexport interface DecryptOptions {\n inputPath: string;\n password: string;\n outputPath?: string;\n onProgress?: (bytesProcessed: number, total: number) => void;\n}\n\nexport interface DecryptResult {\n outputPath: string;\n originalName: string;\n}\n\n// How many bytes to read upfront to parse the header\n// Max header = fixed(67) + 2(len) + 65535(max JSON) — in practice ~200 bytes\nconst HEADER_READ_LIMIT = 4096;\n\nexport async function decryptFile(\n opts: DecryptOptions,\n): Promise<DecryptResult> {\n const { inputPath, password } = opts;\n\n const stat = await fsp.stat(inputPath);\n const totalSize = stat.size;\n\n // Read enough bytes to parse the header\n const fd = await fsp.open(inputPath, \"r\");\n const headerBuf = Buffer.alloc(HEADER_READ_LIMIT);\n const { bytesRead } = await fd.read(headerBuf, 0, HEADER_READ_LIMIT, 0);\n await fd.close();\n\n const header = parseHeader(headerBuf.subarray(0, bytesRead));\n const { salt, iv, authTag, metadata, totalSize: headerSize } = header;\n\n // Derive key from password + salt stored in file\n const key = await deriveKey(password, salt);\n\n // Determine output path\n const outputDir = path.dirname(inputPath);\n const outputPath = opts.outputPath ?? path.join(outputDir, metadata.name);\n\n // Create decipher and set auth tag\n const decipher = createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(authTag);\n\n // Stream ciphertext (skip header bytes) → decipher → output file\n const ciphertextSize = totalSize - headerSize;\n const readStream = createReadStream(inputPath, { start: headerSize });\n\n const progressStream = opts.onProgress\n ? createProgressStream(ciphertextSize, opts.onProgress)\n : null;\n\n const writeStream = createWriteStream(outputPath);\n\n const source = progressStream ? readStream.pipe(progressStream) : readStream;\n\n try {\n await pipeline(source as NodeJS.ReadableStream, decipher, writeStream);\n } catch (err: unknown) {\n // Clean up partial output on auth failure\n await fsp.unlink(outputPath).catch(() => {});\n\n const msg = err instanceof Error ? err.message : String(err);\n if (\n msg.includes(\"Unsupported state\") ||\n msg.includes(\"bad decrypt\") ||\n msg.includes(\"auth\") ||\n msg.includes(\"ERR_CRYPTO\")\n ) {\n throw new Error(\"Invalid password or corrupted file\");\n }\n throw err;\n }\n\n return { outputPath, originalName: metadata.name };\n}\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport chalk from \"chalk\";\nimport { decryptFile } from \"@/crypto/decrypt\";\nimport { ui, createProgressBar } from \"@/cli/ui\";\nimport { formatDuration } from \"@/utils/format\";\n\nexport interface DecryptCommandOptions {\n key: string;\n output?: string;\n}\n\nexport async function runDecrypt(\n filePath: string,\n opts: DecryptCommandOptions,\n): Promise<void> {\n const inputPath = path.resolve(filePath);\n\n if (!existsSync(inputPath)) {\n ui.error(`File not found: ${filePath}`);\n process.exit(1);\n }\n\n ui.info(`Decrypting ${chalk.bold(path.basename(inputPath))}`);\n\n const bar = createProgressBar(\"Decrypting\");\n let barStarted = false;\n const start = Date.now();\n\n try {\n const result = await decryptFile({\n inputPath,\n password: opts.key,\n outputPath: opts.output,\n onProgress(processed, total) {\n if (!barStarted) {\n bar.start(total);\n barStarted = true;\n }\n bar.update(processed, total);\n },\n });\n\n if (barStarted) bar.stop();\n\n const elapsed = Date.now() - start;\n ui.success(\n `Done in ${formatDuration(elapsed)} — ` +\n `restored ${chalk.bold(result.originalName)}`,\n );\n ui.dim(` Output : ${result.outputPath}`);\n } catch (err: unknown) {\n if (barStarted) bar.stop();\n const msg = err instanceof Error ? err.message : String(err);\n\n if (msg === \"Invalid password or corrupted file\") {\n ui.error(\"Invalid password or corrupted file\");\n } else {\n ui.error(`Decryption failed: ${msg}`);\n }\n process.exit(1);\n }\n}\n","import { promises as fsp } from \"node:fs\";\nimport { MAGIC } from \"@/crypto/constants\";\n\n/**\n * Detect whether a file is an enc-x encrypted file by reading\n * the first 4 bytes and comparing against the ENCX magic number.\n * This works regardless of filename or extension.\n */\nexport async function isEncryptedFile(filePath: string): Promise<boolean> {\n let fd: fsp.FileHandle | null = null;\n try {\n fd = await fsp.open(filePath, \"r\");\n const buf = Buffer.alloc(4);\n const { bytesRead } = await fd.read(buf, 0, 4, 0);\n if (bytesRead < 4) return false;\n return buf.equals(MAGIC);\n } catch {\n return false;\n } finally {\n await fd?.close();\n }\n}\n","import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { existsSync } from \"node:fs\";\nimport { runEncrypt } from \"@/cli/commands/encrypt\";\nimport { runDecrypt } from \"@/cli/commands/decrypt\";\nimport { isEncryptedFile } from \"@/utils/detect\";\nimport { ui } from \"@/cli/ui\";\n\nconst program = new Command();\n\nprogram\n .name(\"enc-x\")\n .description(\n chalk.cyan(\"enc-x\") + \" — secure AES-256-GCM file encryption & decryption\",\n )\n .version(\"2.0.0\", \"-v, --version\")\n .argument(\"<file>\", \"file to encrypt or decrypt (auto-detected from content)\")\n .requiredOption(\"-k, --key <password>\", \"password\")\n .option(\"-o, --output <path>\", \"custom output path\")\n .action(async (file: string, opts: { key: string; output?: string }) => {\n if (!existsSync(file)) {\n ui.error(`File not found: ${file}`);\n process.exit(1);\n }\n\n const encrypted = await isEncryptedFile(file);\n\n if (encrypted) {\n await runDecrypt(file, opts);\n } else {\n await runEncrypt(file, opts);\n }\n });\n\nprogram.parse(process.argv);\n"]}
|