wilcocrypt 2.2.0 → 2.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wilcocrypt",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "A encrypting tool",
5
5
  "keywords": [
6
6
  "encrypting",
@@ -10,12 +10,14 @@
10
10
  "author": "computerwilco",
11
11
  "type": "module",
12
12
  "main": "src/wilcocrypt.js",
13
- "bin": "src/cli.js",
13
+ "bin": {
14
+ "wilcocrypt": "src/cli.js"
15
+ },
14
16
  "types": "types/wilcocrypt.d.ts",
15
17
  "scripts": {
16
18
  "test": "node src/cli.js",
17
- "lint": "npx semistandard",
18
- "lint:fix": "npx semistandard --fix",
19
+ "semistandard": "npx semistandard",
20
+ "lint": "npx semistandard --fix; npx prettier --write .",
19
21
  "update": "npx -y Jelmerro/nus",
20
22
  "rollup": "node src/scripts/shebang-fix.js pre && rollup -c && node src/scripts/shebang-fix.js post && node src/scripts/fix-cli-min-import.js && node src/scripts/chmod.js",
21
23
  "sea:blob": "node src/scripts/sea-blob.js",
@@ -28,15 +30,16 @@
28
30
  "clean": "rm -rf dist && rm -rf sea/*.sea && rm -rf release/*"
29
31
  },
30
32
  "dependencies": {
31
- "commander": "14.0.3"
33
+ "commander": "15.0.0"
32
34
  },
33
35
  "devDependencies": {
34
- "@rollup/plugin-commonjs": "29.0.2",
36
+ "@rollup/plugin-commonjs": "29.0.3",
35
37
  "@rollup/plugin-node-resolve": "16.0.3",
36
38
  "@rollup/plugin-replace": "6.0.3",
37
39
  "@rollup/plugin-terser": "1.0.0",
38
- "@types/node": "25.6.0",
39
- "rollup": "4.60.2",
40
+ "@types/node": "25.9.2",
41
+ "prettier": "3.8.3",
42
+ "rollup": "4.61.1",
40
43
  "semistandard": "17.0.0"
41
44
  }
42
45
  }
package/src/cli.js CHANGED
@@ -1,62 +1,62 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import wilcocrypt from './wilcocrypt.js';
2
+ import { Command } from "commander";
3
+ import wilcocrypt from "./wilcocrypt.js";
4
4
 
5
5
  /* =========================
6
6
  Helpers
7
7
  ========================= */
8
8
 
9
- function promptPassword (promptText = 'Password: ') {
9
+ function promptPassword(promptText = "Password: ") {
10
10
  return new Promise((resolve) => {
11
11
  const stdin = process.stdin;
12
12
  const stdout = process.stdout;
13
13
 
14
14
  if (!stdin.isTTY) {
15
15
  throw new wilcocrypt._.WilcoCryptError(
16
- 'Password prompt requires a TTY',
17
- 'NO_TTY'
16
+ "Password prompt requires a TTY",
17
+ "NO_TTY",
18
18
  );
19
19
  }
20
20
 
21
21
  stdout.write(promptText);
22
22
 
23
- let password = '';
23
+ let password = "";
24
24
 
25
25
  stdin.setRawMode(true);
26
26
  stdin.resume();
27
- stdin.setEncoding('utf8');
27
+ stdin.setEncoding("utf8");
28
28
 
29
- function onData (char) {
30
- if (char === '\r' || char === '\n') {
31
- stdout.write('\n');
29
+ function onData(char) {
30
+ if (char === "\r" || char === "\n") {
31
+ stdout.write("\n");
32
32
  stdin.setRawMode(false);
33
33
  stdin.pause();
34
- stdin.removeListener('data', onData);
34
+ stdin.removeListener("data", onData);
35
35
  resolve(password);
36
36
  return;
37
37
  }
38
38
 
39
- if (char === '\u0003') {
40
- stdout.write('\n');
39
+ if (char === "\u0003") {
40
+ stdout.write("\n");
41
41
  stdin.setRawMode(false);
42
42
  stdin.pause();
43
- stdin.removeListener('data', onData);
43
+ stdin.removeListener("data", onData);
44
44
  process.exit(1);
45
45
  }
46
46
 
47
- if (char === '\u007f' || char === '\b') {
47
+ if (char === "\u007f" || char === "\b") {
48
48
  if (password.length > 0) {
49
49
  password = password.slice(0, -1);
50
- stdout.write('\b \b');
50
+ stdout.write("\b \b");
51
51
  }
52
52
  return;
53
53
  }
54
54
 
55
55
  password += char;
56
- stdout.write('*');
56
+ stdout.write("*");
57
57
  }
58
58
 
59
- stdin.on('data', onData);
59
+ stdin.on("data", onData);
60
60
  });
61
61
  }
62
62
 
@@ -67,16 +67,22 @@ function promptPassword (promptText = 'Password: ') {
67
67
  const program = new Command();
68
68
 
69
69
  program
70
- .name('wilcocrypt')
71
- .description('File encryption tool')
72
- .version(wilcocrypt._.VERSION, '--version', 'Show version')
73
-
74
- .option('-e, --encrypt <file>', 'Encrypt file')
75
- .option('-d, --decrypt <file>', 'Decrypt file')
76
- .option('-o, --output <file>', 'Write output to file instead of stdout (decrypt only)')
77
- .option('--stdout', 'Write decrypted output to stdout (default behavior, explicit flag)')
78
-
79
- .helpOption('-h, --help', 'Display help');
70
+ .name("wilcocrypt")
71
+ .description("File encryption tool")
72
+ .version(wilcocrypt._.VERSION, "--version", "Show version")
73
+
74
+ .option("-e, --encrypt <file>", "Encrypt file")
75
+ .option("-d, --decrypt <file>", "Decrypt file")
76
+ .option(
77
+ "-o, --output <file>",
78
+ "Write output to file instead of stdout (decrypt only)",
79
+ )
80
+ .option(
81
+ "--stdout",
82
+ "Write decrypted output to stdout (default behavior, explicit flag)",
83
+ )
84
+
85
+ .helpOption("-h, --help", "Display help");
80
86
 
81
87
  program.parse(process.argv);
82
88
 
@@ -86,27 +92,24 @@ const options = program.opts();
86
92
  Validation
87
93
  ========================= */
88
94
 
89
- const actions = [
90
- options.encrypt,
91
- options.decrypt
92
- ].filter(Boolean);
95
+ const actions = [options.encrypt, options.decrypt].filter(Boolean);
93
96
 
94
97
  if (actions.length === 0) {
95
98
  program.help();
96
99
  }
97
100
 
98
101
  if (actions.length > 1) {
99
- console.error('error: please specify only one action (-e or -d)');
102
+ console.error("error: please specify only one action (-e or -d)");
100
103
  process.exit(1);
101
104
  }
102
105
 
103
106
  if (options.output && options.stdout) {
104
- console.error('error: --output and --stdout are mutually exclusive');
107
+ console.error("error: --output and --stdout are mutually exclusive");
105
108
  process.exit(1);
106
109
  }
107
110
 
108
111
  if (options.output && options.encrypt) {
109
- console.error('error: --output is only supported for decryption');
112
+ console.error("error: --output is only supported for decryption");
110
113
  process.exit(1);
111
114
  }
112
115
 
@@ -117,14 +120,14 @@ if (options.output && options.encrypt) {
117
120
  (async () => {
118
121
  try {
119
122
  if (options.encrypt) {
120
- const password = await promptPassword('Encryption password: ');
123
+ const password = await promptPassword("Encryption password: ");
121
124
  wilcocrypt.encryptFile(options.encrypt, password);
122
125
  console.log(`Encrypted: ${options.encrypt}.enc`);
123
126
  return;
124
127
  }
125
128
 
126
129
  if (options.decrypt) {
127
- const password = await promptPassword('Decryption password: ');
130
+ const password = await promptPassword("Decryption password: ");
128
131
 
129
132
  if (options.output) {
130
133
  wilcocrypt.decryptFile(options.decrypt, password, options.output);
package/src/wilcocrypt.js CHANGED
@@ -1,7 +1,22 @@
1
- import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from 'crypto';
2
- import { gzipSync, gunzipSync, createGzip, createGunzip } from 'zlib';
3
- import { readFileSync, writeFileSync, createReadStream, createWriteStream, promises as fsPromises } from 'fs';
4
- import { pipeline } from 'stream/promises';
1
+ import {
2
+ randomBytes,
3
+ scryptSync,
4
+ scrypt,
5
+ createCipheriv,
6
+ createDecipheriv,
7
+ } from "crypto";
8
+ import { gzipSync, gunzipSync, createGzip, createGunzip } from "zlib";
9
+ import {
10
+ readFileSync,
11
+ writeFileSync,
12
+ createReadStream,
13
+ createWriteStream,
14
+ promises as fsPromises,
15
+ } from "fs";
16
+ import { pipeline } from "stream/promises";
17
+ import { promisify } from "util";
18
+
19
+ const scryptAsync = promisify(scrypt);
5
20
 
6
21
  /**
7
22
  * Main WilcoCrypt namespace.
@@ -25,9 +40,9 @@ class WilcoCryptError extends Error {
25
40
  * @param {string} message - Human-readable error message
26
41
  * @param {string} [code=WILCOCRYPT_ERROR] - Machine-readable error code
27
42
  */
28
- constructor (message, code = 'WILCOCRYPT_ERROR') {
43
+ constructor(message, code = "WILCOCRYPT_ERROR") {
29
44
  super(message);
30
- this.name = 'WilcoCryptError';
45
+ this.name = "WilcoCryptError";
31
46
  this.code = code;
32
47
 
33
48
  if (Error.captureStackTrace) {
@@ -47,7 +62,7 @@ wilcocrypt._.WilcoCryptError = WilcoCryptError;
47
62
  * Must match exactly during decryption.
48
63
  * @type {string}
49
64
  */
50
- wilcocrypt._.VERSION = '2.2.0';
65
+ wilcocrypt._.VERSION = "2.2.0";
51
66
 
52
67
  /**
53
68
  * Minimum allowed password length.
@@ -75,15 +90,15 @@ wilcocrypt._.HEADER = Buffer.from([23, 9, 12, 3, 15, 3, 18, 25, 16, 20]);
75
90
  wilcocrypt._.assertKeyAndIv = function (key, iv) {
76
91
  if (!Buffer.isBuffer(key) || key.length !== 32) {
77
92
  throw new WilcoCryptError(
78
- 'Invalid encryption key (expected 32-byte Buffer)',
79
- 'INVALID_KEY'
93
+ "Invalid encryption key (expected 32-byte Buffer)",
94
+ "INVALID_KEY",
80
95
  );
81
96
  }
82
97
 
83
98
  if (!Buffer.isBuffer(iv) || iv.length !== 12) {
84
99
  throw new WilcoCryptError(
85
- 'Invalid IV (expected 12-byte Buffer)',
86
- 'INVALID_IV'
100
+ "Invalid IV (expected 12-byte Buffer)",
101
+ "INVALID_IV",
87
102
  );
88
103
  }
89
104
  };
@@ -95,10 +110,13 @@ wilcocrypt._.assertKeyAndIv = function (key, iv) {
95
110
  * @throws {WilcoCryptError}
96
111
  */
97
112
  wilcocrypt._.assertPassword = function (password) {
98
- if (typeof password !== 'string' || password.length < wilcocrypt._.MIN_PASSWORD_LENGTH) {
113
+ if (
114
+ typeof password !== "string" ||
115
+ password.length < wilcocrypt._.MIN_PASSWORD_LENGTH
116
+ ) {
99
117
  throw new WilcoCryptError(
100
118
  `Password must be at least ${wilcocrypt._.MIN_PASSWORD_LENGTH} characters`,
101
- 'WEAK_PASSWORD'
119
+ "WEAK_PASSWORD",
102
120
  );
103
121
  }
104
122
  };
@@ -136,15 +154,12 @@ wilcocrypt._.constantTimeEqual = function (a, b) {
136
154
  wilcocrypt._.encryptData = function (plainData, key, iv) {
137
155
  wilcocrypt._.assertKeyAndIv(key, iv);
138
156
 
139
- const cipher = createCipheriv('aes-256-gcm', key, iv);
140
- const encrypted = Buffer.concat([
141
- cipher.update(plainData),
142
- cipher.final()
143
- ]);
157
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
158
+ const encrypted = Buffer.concat([cipher.update(plainData), cipher.final()]);
144
159
 
145
160
  return {
146
161
  ciphertext: encrypted,
147
- authTag: cipher.getAuthTag()
162
+ authTag: cipher.getAuthTag(),
148
163
  };
149
164
  };
150
165
 
@@ -161,17 +176,14 @@ wilcocrypt._.decryptData = function (cipherBuffer, authTagBuffer, key, iv) {
161
176
  wilcocrypt._.assertKeyAndIv(key, iv);
162
177
 
163
178
  try {
164
- const decipher = createDecipheriv('aes-256-gcm', key, iv);
179
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
165
180
  decipher.setAuthTag(authTagBuffer);
166
181
 
167
- return Buffer.concat([
168
- decipher.update(cipherBuffer),
169
- decipher.final()
170
- ]);
182
+ return Buffer.concat([decipher.update(cipherBuffer), decipher.final()]);
171
183
  } catch {
172
184
  throw new WilcoCryptError(
173
- 'Decryption failed (invalid password, corrupted data, or tampered file)',
174
- 'DECRYPTION_FAILED'
185
+ "Decryption failed (invalid password, corrupted data, or tampered file)",
186
+ "DECRYPTION_FAILED",
175
187
  );
176
188
  }
177
189
  };
@@ -210,7 +222,47 @@ wilcocrypt.encryptData = function (plaindata, password, gzip = true) {
210
222
  salt, // 16 bytes
211
223
  iv, // 12 bytes
212
224
  ciphertext, // variable
213
- authTag // 16 bytes (at the end for streaming compatibility)
225
+ authTag, // 16 bytes (at the end for streaming compatibility)
226
+ ]);
227
+ };
228
+
229
+ /**
230
+ * Encrypts data asynchronously using password-based AES-256-GCM.
231
+ *
232
+ * Output format:
233
+ * [HEADER (10 bytes)] + [VERSION (dynamic)] + [salt (16)] + [iv (12)] + [ciphertext] + [authTag (16)]
234
+ *
235
+ * @param {Buffer} plaindata - Raw data to encrypt
236
+ * @param {string} password - Password used for key derivation
237
+ * @param {boolean} [gzip=true] - Whether to compress data before encryption
238
+ * @returns {Promise<Buffer>} Binary-encoded encrypted payload
239
+ * @throws {WilcoCryptError} If password is invalid
240
+ */
241
+ wilcocrypt.encryptDataAsync = async function (
242
+ plaindata,
243
+ password,
244
+ gzip = true,
245
+ ) {
246
+ wilcocrypt._.assertPassword(password);
247
+
248
+ const gzipData = gzip ? gzipSync(plaindata) : plaindata;
249
+
250
+ const iv = randomBytes(12);
251
+ const salt = randomBytes(16);
252
+
253
+ const key = await scryptAsync(password, salt, 32);
254
+
255
+ const { ciphertext, authTag } = wilcocrypt._.encryptData(gzipData, key, iv);
256
+
257
+ const versionBuf = Buffer.from(wilcocrypt._.VERSION);
258
+
259
+ return Buffer.concat([
260
+ wilcocrypt._.HEADER,
261
+ versionBuf,
262
+ salt,
263
+ iv,
264
+ ciphertext,
265
+ authTag,
214
266
  ]);
215
267
  };
216
268
 
@@ -232,22 +284,31 @@ wilcocrypt.decryptData = function (encryptedBuffer, password, gzip = true) {
232
284
  const versionBuf = Buffer.from(wilcocrypt._.VERSION);
233
285
  let offset = 0;
234
286
 
235
- const fileHeader = encryptedBuffer.subarray(offset, offset += wilcocrypt._.HEADER.length);
287
+ const fileHeader = encryptedBuffer.subarray(
288
+ offset,
289
+ (offset += wilcocrypt._.HEADER.length),
290
+ );
236
291
  if (!fileHeader.equals(wilcocrypt._.HEADER)) {
237
- throw new WilcoCryptError('Invalid WilcoCrypt header', 'INVALID_HEADER');
292
+ throw new WilcoCryptError("Invalid WilcoCrypt header", "INVALID_HEADER");
238
293
  }
239
294
 
240
- const fileVersion = encryptedBuffer.subarray(offset, offset += versionBuf.length);
295
+ const fileVersion = encryptedBuffer.subarray(
296
+ offset,
297
+ (offset += versionBuf.length),
298
+ );
241
299
  if (!fileVersion.equals(versionBuf)) {
242
- throw new WilcoCryptError('Version mismatch', 'VERSION_MISMATCH');
300
+ throw new WilcoCryptError("Version mismatch", "VERSION_MISMATCH");
243
301
  }
244
302
 
245
- const salt = encryptedBuffer.subarray(offset, offset += 16);
246
- const iv = encryptedBuffer.subarray(offset, offset += 12);
303
+ const salt = encryptedBuffer.subarray(offset, (offset += 16));
304
+ const iv = encryptedBuffer.subarray(offset, (offset += 12));
247
305
 
248
306
  // authTag are the last 16 bytes; ciphertext is everything in between
249
307
  const authTag = encryptedBuffer.subarray(encryptedBuffer.length - 16);
250
- const ciphertext = encryptedBuffer.subarray(offset, encryptedBuffer.length - 16);
308
+ const ciphertext = encryptedBuffer.subarray(
309
+ offset,
310
+ encryptedBuffer.length - 16,
311
+ );
251
312
 
252
313
  const key = scryptSync(password, salt, 32);
253
314
 
@@ -256,6 +317,63 @@ wilcocrypt.decryptData = function (encryptedBuffer, password, gzip = true) {
256
317
  return gzip ? gunzipSync(decrypted) : decrypted;
257
318
  };
258
319
 
320
+ /**
321
+ * Decrypts encrypted data asynchronously using password-based AES-256-GCM.
322
+ *
323
+ * Validates internal header and version, then extracts:
324
+ * salt, iv, authTag and ciphertext from the binary payload.
325
+ *
326
+ * @param {Buffer} encryptedBuffer - Binary-encoded encrypted payload
327
+ * @param {string} password - Password used for decryption
328
+ * @param {boolean} [gzip=true] - Whether to decompress after decryption
329
+ * @returns {Promise<Buffer>} Decrypted raw data
330
+ * @throws {WilcoCryptError} On invalid header, version mismatch, wrong password, or corrupted data
331
+ */
332
+ wilcocrypt.decryptDataAsync = async function (
333
+ encryptedBuffer,
334
+ password,
335
+ gzip = true,
336
+ ) {
337
+ wilcocrypt._.assertPassword(password);
338
+
339
+ const versionBuf = Buffer.from(wilcocrypt._.VERSION);
340
+ let offset = 0;
341
+
342
+ const fileHeader = encryptedBuffer.subarray(
343
+ offset,
344
+ (offset += wilcocrypt._.HEADER.length),
345
+ );
346
+
347
+ if (!fileHeader.equals(wilcocrypt._.HEADER)) {
348
+ throw new WilcoCryptError("Invalid WilcoCrypt header", "INVALID_HEADER");
349
+ }
350
+
351
+ const fileVersion = encryptedBuffer.subarray(
352
+ offset,
353
+ (offset += versionBuf.length),
354
+ );
355
+
356
+ if (!fileVersion.equals(versionBuf)) {
357
+ throw new WilcoCryptError("Version mismatch", "VERSION_MISMATCH");
358
+ }
359
+
360
+ const salt = encryptedBuffer.subarray(offset, (offset += 16));
361
+ const iv = encryptedBuffer.subarray(offset, (offset += 12));
362
+
363
+ const authTag = encryptedBuffer.subarray(encryptedBuffer.length - 16);
364
+
365
+ const ciphertext = encryptedBuffer.subarray(
366
+ offset,
367
+ encryptedBuffer.length - 16,
368
+ );
369
+
370
+ const key = await scryptAsync(password, salt, 32);
371
+
372
+ const decrypted = wilcocrypt._.decryptData(ciphertext, authTag, key, iv);
373
+
374
+ return gzip ? gunzipSync(decrypted) : decrypted;
375
+ };
376
+
259
377
  /**
260
378
  * Encrypts a file and writes the result to `<filePath>.enc`.
261
379
  *
@@ -271,6 +389,27 @@ wilcocrypt.encryptFile = function (filePath, password, gzip = true) {
271
389
  writeFileSync(`${filePath}.enc`, encryptedData);
272
390
  };
273
391
 
392
+ /**
393
+ * Encrypts a file asynchronously and writes the result to `<filePath>.enc`.
394
+ *
395
+ * @param {string} filePath - Path to the file to encrypt
396
+ * @param {string} password - Password used for encryption
397
+ * @param {boolean} [gzip=true] - Whether to compress before encryption
398
+ * @returns {Promise<void>}
399
+ * @throws {WilcoCryptError} If password is invalid
400
+ */
401
+ wilcocrypt.encryptFileAsync = async function (filePath, password, gzip = true) {
402
+ const fileData = await fsPromises.readFile(filePath);
403
+
404
+ const encryptedData = await wilcocrypt.encryptDataAsync(
405
+ fileData,
406
+ password,
407
+ gzip,
408
+ );
409
+
410
+ await fsPromises.writeFile(`${filePath}.enc`, encryptedData);
411
+ };
412
+
274
413
  /**
275
414
  * Decrypts an encrypted `.enc` file.
276
415
  *
@@ -285,17 +424,22 @@ wilcocrypt.encryptFile = function (filePath, password, gzip = true) {
285
424
  * @returns {Buffer|undefined} Decrypted file contents, or undefined if outputPath was given
286
425
  * @throws {WilcoCryptError} If file extension is invalid or decryption fails
287
426
  */
288
- wilcocrypt.decryptFile = function (filePath, password, outputPath, gzip = true) {
427
+ wilcocrypt.decryptFile = function (
428
+ filePath,
429
+ password,
430
+ outputPath,
431
+ gzip = true,
432
+ ) {
289
433
  // Support legacy 3-argument form: decryptFile(filePath, password, gzip?)
290
- if (typeof outputPath === 'boolean') {
434
+ if (typeof outputPath === "boolean") {
291
435
  gzip = outputPath;
292
436
  outputPath = undefined;
293
437
  }
294
438
 
295
- if (!filePath.endsWith('.enc')) {
439
+ if (!filePath.endsWith(".enc")) {
296
440
  throw new WilcoCryptError(
297
- 'Invalid file extension (expected .enc)',
298
- 'INVALID_FILE_EXTENSION'
441
+ "Invalid file extension (expected .enc)",
442
+ "INVALID_FILE_EXTENSION",
299
443
  );
300
444
  }
301
445
 
@@ -310,6 +454,53 @@ wilcocrypt.decryptFile = function (filePath, password, outputPath, gzip = true)
310
454
  return decrypted;
311
455
  };
312
456
 
457
+ /**
458
+ * Decrypts an encrypted `.enc` file asynchronously.
459
+ *
460
+ * If `outputPath` is provided, the decrypted data is written to that file
461
+ * and `undefined` is returned. Otherwise the decrypted Buffer is returned.
462
+ *
463
+ * @param {string} filePath - Path to the `.enc` file
464
+ * @param {string} password - Password used for decryption
465
+ * @param {string|boolean} [outputPath] - Optional output path
466
+ * @param {boolean} [gzip=true] - Whether to decompress after decryption
467
+ * @returns {Promise<Buffer|undefined>}
468
+ * @throws {WilcoCryptError}
469
+ */
470
+ wilcocrypt.decryptFileAsync = async function (
471
+ filePath,
472
+ password,
473
+ outputPath,
474
+ gzip = true,
475
+ ) {
476
+ if (typeof outputPath === "boolean") {
477
+ gzip = outputPath;
478
+ outputPath = undefined;
479
+ }
480
+
481
+ if (!filePath.endsWith(".enc")) {
482
+ throw new WilcoCryptError(
483
+ "Invalid file extension (expected .enc)",
484
+ "INVALID_FILE_EXTENSION",
485
+ );
486
+ }
487
+
488
+ const encryptedData = await fsPromises.readFile(filePath);
489
+
490
+ const decrypted = await wilcocrypt.decryptDataAsync(
491
+ encryptedData,
492
+ password,
493
+ gzip,
494
+ );
495
+
496
+ if (outputPath) {
497
+ await fsPromises.writeFile(outputPath, decrypted);
498
+ return;
499
+ }
500
+
501
+ return decrypted;
502
+ };
503
+
313
504
  /**
314
505
  * Encrypts a file using streams and writes the result to `outputPath`.
315
506
  * Memory-efficient alternative to `encryptFile` for large files.
@@ -324,7 +515,12 @@ wilcocrypt.decryptFile = function (filePath, password, outputPath, gzip = true)
324
515
  * @returns {Promise<void>}
325
516
  * @throws {WilcoCryptError} If password is invalid
326
517
  */
327
- wilcocrypt.encryptFileStream = async function (inputPath, outputPath, password, gzip = true) {
518
+ wilcocrypt.encryptFileStream = async function (
519
+ inputPath,
520
+ outputPath,
521
+ password,
522
+ gzip = true,
523
+ ) {
328
524
  wilcocrypt._.assertPassword(password);
329
525
 
330
526
  const salt = randomBytes(16);
@@ -332,7 +528,7 @@ wilcocrypt.encryptFileStream = async function (inputPath, outputPath, password,
332
528
  const key = scryptSync(password, salt, 32);
333
529
  const versionBuf = Buffer.from(wilcocrypt._.VERSION);
334
530
 
335
- const cipher = createCipheriv('aes-256-gcm', key, iv);
531
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
336
532
  const writeStream = createWriteStream(outputPath);
337
533
 
338
534
  writeStream.write(wilcocrypt._.HEADER);
@@ -362,10 +558,15 @@ wilcocrypt.encryptFileStream = async function (inputPath, outputPath, password,
362
558
  * @returns {Promise<void>}
363
559
  * @throws {WilcoCryptError} On invalid header, version mismatch, or decryption/integrity failure
364
560
  */
365
- wilcocrypt.decryptFileStream = async function (inputPath, outputPath, password, gzip = true) {
561
+ wilcocrypt.decryptFileStream = async function (
562
+ inputPath,
563
+ outputPath,
564
+ password,
565
+ gzip = true,
566
+ ) {
366
567
  wilcocrypt._.assertPassword(password);
367
568
 
368
- const handle = await fsPromises.open(inputPath, 'r');
569
+ const handle = await fsPromises.open(inputPath, "r");
369
570
  const versionBuf = Buffer.from(wilcocrypt._.VERSION);
370
571
 
371
572
  const headLen = wilcocrypt._.HEADER.length;
@@ -377,19 +578,23 @@ wilcocrypt.decryptFileStream = async function (inputPath, outputPath, password,
377
578
  const iv = Buffer.alloc(12);
378
579
 
379
580
  let currentPos = 0;
380
- await handle.read(headerCheck, 0, headLen, currentPos); currentPos += headLen;
381
- await handle.read(versionCheck, 0, verLen, currentPos); currentPos += verLen;
382
- await handle.read(salt, 0, 16, currentPos); currentPos += 16;
383
- await handle.read(iv, 0, 12, currentPos); currentPos += 12;
581
+ await handle.read(headerCheck, 0, headLen, currentPos);
582
+ currentPos += headLen;
583
+ await handle.read(versionCheck, 0, verLen, currentPos);
584
+ currentPos += verLen;
585
+ await handle.read(salt, 0, 16, currentPos);
586
+ currentPos += 16;
587
+ await handle.read(iv, 0, 12, currentPos);
588
+ currentPos += 12;
384
589
 
385
590
  if (!headerCheck.equals(wilcocrypt._.HEADER)) {
386
591
  await handle.close();
387
- throw new WilcoCryptError('Invalid WilcoCrypt header', 'INVALID_HEADER');
592
+ throw new WilcoCryptError("Invalid WilcoCrypt header", "INVALID_HEADER");
388
593
  }
389
594
 
390
595
  if (!versionCheck.equals(versionBuf)) {
391
596
  await handle.close();
392
- throw new WilcoCryptError('Version mismatch', 'VERSION_MISMATCH');
597
+ throw new WilcoCryptError("Version mismatch", "VERSION_MISMATCH");
393
598
  }
394
599
 
395
600
  const stats = await handle.stat();
@@ -397,10 +602,12 @@ wilcocrypt.decryptFileStream = async function (inputPath, outputPath, password,
397
602
  await handle.read(authTag, 0, 16, stats.size - 16);
398
603
 
399
604
  const key = scryptSync(password, salt, 32);
400
- const decipher = createDecipheriv('aes-256-gcm', key, iv);
605
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
401
606
  decipher.setAuthTag(authTag);
402
607
 
403
- const pipelineSteps = [createReadStream(inputPath, { start: currentPos, end: stats.size - 17 })];
608
+ const pipelineSteps = [
609
+ createReadStream(inputPath, { start: currentPos, end: stats.size - 17 }),
610
+ ];
404
611
  pipelineSteps.push(decipher);
405
612
  if (gzip) pipelineSteps.push(createGunzip());
406
613
  pipelineSteps.push(createWriteStream(outputPath));
@@ -411,8 +618,8 @@ wilcocrypt.decryptFileStream = async function (inputPath, outputPath, password,
411
618
  await handle.close();
412
619
  await fsPromises.unlink(outputPath);
413
620
  throw new WilcoCryptError(
414
- 'Decryption failed (invalid password, corrupted data, or tampered file)',
415
- 'DECRYPTION_FAILED'
621
+ "Decryption failed (invalid password, corrupted data, or tampered file)",
622
+ "DECRYPTION_FAILED",
416
623
  );
417
624
  }
418
625