wilcocrypt 2.1.1 → 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/DOCS.md ADDED
@@ -0,0 +1,527 @@
1
+ # WilcoCrypt Documentation
2
+
3
+ Complete reference for the WilcoCrypt API, CLI, and binary format.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [Quick Start](#quick-start)
11
+ - [API Reference](#api-reference)
12
+ - [encryptData](#encryptdata)
13
+ - [decryptData](#decryptdata)
14
+ - [encryptFile](#encryptfile)
15
+ - [decryptFile](#decryptfile)
16
+ - [encryptDataAsync](#encryptdataasync)
17
+ - [decryptDataAsync](#decryptdataasync)
18
+ - [encryptFileAsync](#encryptfileasync)
19
+ - [decryptFileAsync](#decryptfileasync)
20
+ - [encryptFileStream](#encryptfilestream)
21
+ - [decryptFileStream](#decryptfilestream)
22
+ - [Internal Namespace (`_`)](#internal-namespace-_)
23
+ - [CLI Reference](#cli-reference)
24
+ - [Binary Payload Format](#binary-payload-format)
25
+ - [Error Handling](#error-handling)
26
+ - [TypeScript](#typescript)
27
+ - [Security Notes](#security-notes)
28
+
29
+ ---
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install wilcocrypt
35
+ ```
36
+
37
+ Requires Node.js 18 or later (uses `stream/promises` and `fs/promises`).
38
+
39
+ ---
40
+
41
+ ## Quick Start
42
+
43
+ ```js
44
+ import wilcocrypt from "wilcocrypt";
45
+
46
+ // Encrypt a Buffer
47
+ const data = Buffer.from("Hello, world!");
48
+ const encrypted = wilcocrypt.encryptData(data, "my-password");
49
+
50
+ // Decrypt it back
51
+ const decrypted = wilcocrypt.decryptData(encrypted, "my-password");
52
+ console.log(decrypted.toString()); // Hello, world!
53
+
54
+ // Encrypt a file (writes file.txt.enc)
55
+ wilcocrypt.encryptFile("file.txt", "my-password");
56
+
57
+ // Decrypt a file (returns Buffer)
58
+ const contents = wilcocrypt.decryptFile("file.txt.enc", "my-password");
59
+
60
+ // Decrypt a file directly to disk
61
+ wilcocrypt.decryptFile("file.txt.enc", "my-password", "output.txt");
62
+
63
+ // Async API
64
+ const encryptedAsync = await wilcocrypt.encryptDataAsync(
65
+ Buffer.from("Hello!"),
66
+ "my-password",
67
+ );
68
+
69
+ const decryptedAsync = await wilcocrypt.decryptDataAsync(
70
+ encryptedAsync,
71
+ "my-password",
72
+ );
73
+
74
+ // Async file API
75
+ await wilcocrypt.encryptFileAsync(
76
+ "file.txt",
77
+ "my-password",
78
+ );
79
+
80
+ await wilcocrypt.decryptFileAsync(
81
+ "file.txt.enc",
82
+ "my-password",
83
+ "output.txt",
84
+ );
85
+ ```
86
+
87
+ ---
88
+
89
+ ## API Reference
90
+
91
+ ### `encryptData(plaindata, password, gzip?)`
92
+
93
+ Encrypts a Buffer using password-based AES-256-GCM. The password is never stored; a random salt is generated for every encryption call.
94
+
95
+ | Parameter | Type | Default | Description |
96
+ | ----------- | --------- | ------- | ------------------------------------------ |
97
+ | `plaindata` | `Buffer` | — | Raw data to encrypt |
98
+ | `password` | `string` | — | Password for key derivation (min. 6 chars) |
99
+ | `gzip` | `boolean` | `true` | Compress data before encryption |
100
+
101
+ **Returns:** `Buffer` — the encrypted payload in the [binary format](#binary-payload-format).
102
+
103
+ **Throws:** `WilcoCryptError` with code `WEAK_PASSWORD` if the password is too short.
104
+
105
+ ```js
106
+ const encrypted = wilcocrypt.encryptData(Buffer.from("secret"), "passw0rd");
107
+ ```
108
+
109
+ ---
110
+
111
+ ### `decryptData(encryptedBuffer, password, gzip?)`
112
+
113
+ Decrypts a payload produced by `encryptData`. Validates the header and version before attempting decryption.
114
+
115
+ | Parameter | Type | Default | Description |
116
+ | ----------------- | --------- | ------- | ------------------------------- |
117
+ | `encryptedBuffer` | `Buffer` | — | Payload from `encryptData` |
118
+ | `password` | `string` | — | Password used during encryption |
119
+ | `gzip` | `boolean` | `true` | Decompress after decryption |
120
+
121
+ **Returns:** `Buffer` — the original plaintext data.
122
+
123
+ **Throws:**
124
+
125
+ | Code | Reason |
126
+ | ------------------- | ---------------------------------------------- |
127
+ | `WEAK_PASSWORD` | Password shorter than 6 characters |
128
+ | `INVALID_HEADER` | Not a valid WilcoCrypt payload |
129
+ | `VERSION_MISMATCH` | Payload was encrypted with a different version |
130
+ | `DECRYPTION_FAILED` | Wrong password, tampered or corrupt data |
131
+
132
+ ```js
133
+ const plain = wilcocrypt.decryptData(encrypted, "passw0rd");
134
+ ```
135
+
136
+ ---
137
+
138
+ ### `encryptFile(filePath, password, gzip?)`
139
+
140
+ Reads a file, encrypts it, and writes the result to `<filePath>.enc`. Uses `encryptData` internally, so the entire file is loaded into memory. For large files, use [`encryptFileStream`](#encryptfilestream) instead.
141
+
142
+ | Parameter | Type | Default | Description |
143
+ | ---------- | --------- | ------- | --------------------------- |
144
+ | `filePath` | `string` | — | Path to the source file |
145
+ | `password` | `string` | — | Password for key derivation |
146
+ | `gzip` | `boolean` | `true` | Compress before encryption |
147
+
148
+ **Returns:** `void`
149
+
150
+ ```js
151
+ wilcocrypt.encryptFile("document.pdf", "passw0rd");
152
+ // Creates document.pdf.enc
153
+ ```
154
+
155
+ ---
156
+
157
+ ### `decryptFile(filePath, password, outputPath?, gzip?)`
158
+
159
+ Decrypts a `.enc` file. If `outputPath` is provided, the result is written to disk and `undefined` is returned. Otherwise the decrypted `Buffer` is returned.
160
+
161
+ | Parameter | Type | Default | Description |
162
+ | ------------ | --------- | ----------- | ------------------------------------------ |
163
+ | `filePath` | `string` | — | Path to the `.enc` file |
164
+ | `password` | `string` | — | Password used during encryption |
165
+ | `outputPath` | `string` | `undefined` | Optional path to write decrypted output to |
166
+ | `gzip` | `boolean` | `true` | Decompress after decryption |
167
+
168
+ > The legacy 3-argument form `decryptFile(filePath, password, gzip)` is still fully supported, but will be deprecated in the next release.
169
+
170
+ **Returns:** `Buffer` when no `outputPath` is given, `undefined` otherwise.
171
+
172
+ **Throws:** `WilcoCryptError` with code `INVALID_FILE_EXTENSION` if `filePath` does not end in `.enc`.
173
+
174
+ ```js
175
+ // Return as Buffer
176
+ const buf = wilcocrypt.decryptFile("document.pdf.enc", "passw0rd");
177
+
178
+ // Write directly to disk
179
+ wilcocrypt.decryptFile("document.pdf.enc", "passw0rd", "document.pdf");
180
+ ```
181
+
182
+ ---
183
+
184
+ ### `encryptDataAsync(plaindata, password, gzip?)`
185
+
186
+ Asynchronous version of `encryptData`.
187
+
188
+ Encrypts a Buffer using password-based AES-256-GCM. The password is never stored; a random salt is generated for every encryption call.
189
+
190
+ | Parameter | Type | Default | Description |
191
+ | ----------- | --------- | ------- | ------------------------------------------ |
192
+ | `plaindata` | `Buffer` | — | Raw data to encrypt |
193
+ | `password` | `string` | — | Password for key derivation (min. 6 chars) |
194
+ | `gzip` | `boolean` | `true` | Compress data before encryption |
195
+
196
+ **Returns:** `Promise<Buffer>` — the encrypted payload in the binary format.
197
+
198
+ **Throws:** `WilcoCryptError` with code `WEAK_PASSWORD` if the password is too short.
199
+
200
+ ```js
201
+ const encrypted = await wilcocrypt.encryptDataAsync(
202
+ Buffer.from("secret"),
203
+ "passw0rd",
204
+ );
205
+ ```
206
+
207
+ ---
208
+
209
+ ### `decryptDataAsync(encryptedBuffer, password, gzip?)`
210
+
211
+ Asynchronous version of `decryptData`.
212
+
213
+ Decrypts a payload produced by `encryptDataAsync` or `encryptData`. Validates the header and version before attempting decryption.
214
+
215
+ | Parameter | Type | Default | Description |
216
+ | ----------------- | --------- | ------- | ------------------------------- |
217
+ | `encryptedBuffer` | `Buffer` | — | Payload from `encryptData` |
218
+ | `password` | `string` | — | Password used during encryption |
219
+ | `gzip` | `boolean` | `true` | Decompress after decryption |
220
+
221
+ **Returns:** `Promise<Buffer>` — the original plaintext data.
222
+
223
+ **Throws:**
224
+
225
+ | Code | Reason |
226
+ | ------------------- | ---------------------------------------------- |
227
+ | `WEAK_PASSWORD` | Password shorter than 6 characters |
228
+ | `INVALID_HEADER` | Not a valid WilcoCrypt payload |
229
+ | `VERSION_MISMATCH` | Payload was encrypted with a different version |
230
+ | `DECRYPTION_FAILED` | Wrong password, tampered or corrupt data |
231
+
232
+ ```js
233
+ const plain = await wilcocrypt.decryptDataAsync(
234
+ encrypted,
235
+ "passw0rd",
236
+ );
237
+ ```
238
+
239
+ ---
240
+
241
+ ### `encryptFileAsync(filePath, password, gzip?)`
242
+
243
+ Asynchronous version of `encryptFile`.
244
+
245
+ Reads a file, encrypts it, and writes the result to `<filePath>.enc`.
246
+
247
+ | Parameter | Type | Default | Description |
248
+ | ---------- | --------- | ------- | --------------------------- |
249
+ | `filePath` | `string` | — | Path to the source file |
250
+ | `password` | `string` | — | Password for key derivation |
251
+ | `gzip` | `boolean` | `true` | Compress before encryption |
252
+
253
+ **Returns:** `Promise<void>`
254
+
255
+ ```js
256
+ await wilcocrypt.encryptFileAsync(
257
+ "document.pdf",
258
+ "passw0rd",
259
+ );
260
+ ```
261
+
262
+ ---
263
+
264
+ ### `decryptFileAsync(filePath, password, outputPath?, gzip?)`
265
+
266
+ Asynchronous version of `decryptFile`.
267
+
268
+ If `outputPath` is provided, the result is written to disk and `undefined` is returned. Otherwise the decrypted `Buffer` is returned.
269
+
270
+ | Parameter | Type | Default | Description |
271
+ | ------------ | --------- | ----------- | ------------------------------------------ |
272
+ | `filePath` | `string` | — | Path to the `.enc` file |
273
+ | `password` | `string` | — | Password used during encryption |
274
+ | `outputPath` | `string` | `undefined` | Optional path to write decrypted output to |
275
+ | `gzip` | `boolean` | `true` | Decompress after decryption |
276
+
277
+ **Returns:** `Promise<Buffer>` when no `outputPath` is given, `Promise<undefined>` otherwise.
278
+
279
+ **Throws:** Same error codes as `decryptFile`.
280
+
281
+ ```js
282
+ // Return as Buffer
283
+ const buf = await wilcocrypt.decryptFileAsync(
284
+ "document.pdf.enc",
285
+ "passw0rd",
286
+ );
287
+
288
+ // Write directly to disk
289
+ await wilcocrypt.decryptFileAsync(
290
+ "document.pdf.enc",
291
+ "passw0rd",
292
+ "document.pdf",
293
+ );
294
+ ```
295
+
296
+ ---
297
+
298
+ ### `encryptFileStream(inputPath, outputPath, password, gzip?)`
299
+
300
+ Streaming equivalent of `encryptFile`. Reads from `inputPath` and writes to `outputPath` chunk by chunk — suitable for large files where loading everything into memory is impractical.
301
+
302
+ | Parameter | Type | Default | Description |
303
+ | ------------ | --------- | ------- | ----------------------------- |
304
+ | `inputPath` | `string` | — | Path to the source file |
305
+ | `outputPath` | `string` | — | Path for the encrypted output |
306
+ | `password` | `string` | — | Password for key derivation |
307
+ | `gzip` | `boolean` | `true` | Compress before encryption |
308
+
309
+ **Returns:** `Promise<void>`
310
+
311
+ ```js
312
+ await wilcocrypt.encryptFileStream(
313
+ "bigfile.zip",
314
+ "bigfile.zip.enc",
315
+ "passw0rd",
316
+ );
317
+ ```
318
+
319
+ ---
320
+
321
+ ### `decryptFileStream(inputPath, outputPath, password, gzip?)`
322
+
323
+ Streaming equivalent of `decryptFile`. Header and version are validated before the stream starts. If decryption fails at any point, the partially written output file is deleted automatically.
324
+
325
+ | Parameter | Type | Default | Description |
326
+ | ------------ | --------- | ------- | --------------------------------- |
327
+ | `inputPath` | `string` | — | Path to the `.enc` file |
328
+ | `outputPath` | `string` | — | Path to write decrypted output to |
329
+ | `password` | `string` | — | Password used during encryption |
330
+ | `gzip` | `boolean` | `true` | Decompress after decryption |
331
+
332
+ **Returns:** `Promise<void>`
333
+
334
+ **Throws:** Same error codes as `decryptData`, plus automatic cleanup of `outputPath` on failure.
335
+
336
+ ```js
337
+ await wilcocrypt.decryptFileStream(
338
+ "bigfile.zip.enc",
339
+ "bigfile.zip",
340
+ "passw0rd",
341
+ );
342
+ ```
343
+
344
+ ---
345
+
346
+ ### Internal Namespace (`_`)
347
+
348
+ The `wilcocrypt._` namespace exposes internal helpers. These are not intended for normal use but are part of the public surface for advanced use cases and testing.
349
+
350
+ | Member | Type | Description |
351
+ | ----------------------------------------------------- | ---------- | ------------------------------------------------------------- |
352
+ | `_.VERSION` | `string` | Current version string, embedded in every payload |
353
+ | `_.MIN_PASSWORD_LENGTH` | `number` | Minimum accepted password length (6) |
354
+ | `_.HEADER` | `Buffer` | 10-byte magic bytes identifying a WilcoCrypt payload |
355
+ | `_.WilcoCryptError` | `class` | The error class (also importable from TypeScript types) |
356
+ | `_.assertKeyAndIv(key, iv)` | `function` | Throws if key or IV are not valid Buffers of the right length |
357
+ | `_.assertPassword(password)` | `function` | Throws `WEAK_PASSWORD` if password is too short |
358
+ | `_.constantTimeEqual(a, b)` | `function` | Constant-time Buffer comparison, returns `boolean` |
359
+ | `_.encryptData(plainData, key, iv)` | `function` | Raw AES-256-GCM encryption, returns `{ ciphertext, authTag }` |
360
+ | `_.decryptData(cipherBuffer, authTagBuffer, key, iv)` | `function` | Raw AES-256-GCM decryption, returns `Buffer` |
361
+
362
+ ---
363
+
364
+ ## CLI Reference
365
+
366
+ Install globally or use via `npx`:
367
+
368
+ ```bash
369
+ npm install -g wilcocrypt
370
+ wilcocrypt --help
371
+ ```
372
+
373
+ ### Options
374
+
375
+ | Flag | Description |
376
+ | ---------------------- | ----------------------------------------------------- |
377
+ | `-e, --encrypt <file>` | Encrypt the given file, writes `<file>.enc` |
378
+ | `-d, --decrypt <file>` | Decrypt the given `.enc` file |
379
+ | `-o, --output <file>` | Write decrypted output to `<file>` instead of stdout |
380
+ | `--stdout` | Explicitly write decrypted output to stdout (default) |
381
+ | `--version` | Show WilcoCrypt version |
382
+ | `-h, --help` | Show help |
383
+
384
+ Only one of `-e` or `-d` may be used at a time. The `--output` and `--stdout` flags are mutually exclusive. `--output` is only valid with `-d`.
385
+
386
+ ### Examples
387
+
388
+ ```bash
389
+ # Encrypt a file
390
+ wilcocrypt -e secret.txt
391
+ # → prompts for password, writes secret.txt.enc
392
+
393
+ # Decrypt to stdout (pipe-friendly)
394
+ wilcocrypt -d secret.txt.enc
395
+ # → prompts for password, writes to stdout
396
+
397
+ # Decrypt to a file
398
+ wilcocrypt -d secret.txt.enc -o secret.txt
399
+ # → prompts for password, writes to secret.txt
400
+ ```
401
+
402
+ Passwords are entered interactively with character masking (`*`). The CLI requires a TTY; piping passwords in is intentionally not supported.
403
+
404
+ ---
405
+
406
+ ## Binary Payload Format
407
+
408
+ Every payload produced in WilcoCrypt v2.2.x has the following binary layout:
409
+
410
+ ```
411
+ [ HEADER ] 10 bytes — magic bytes: 23 9 12 3 15 3 18 25 16 20
412
+ [ VERSION ] dynamic — UTF-8 version string (e.g. "2.2.0"), 5 bytes for current version
413
+ [ salt ] 16 bytes — random salt for scrypt key derivation
414
+ [ iv ] 12 bytes — random IV for AES-256-GCM
415
+ [ ciphertext] variable — AES-256-GCM encrypted (and optionally gzip-compressed) data
416
+ [ authTag ] 16 bytes — GCM authentication tag
417
+ ```
418
+
419
+ The auth tag is placed at the end to allow the streaming API to append it after the pipeline finishes, without needing to seek backwards in the output stream.
420
+
421
+ > **Compatibility note:** The format changed between v2.1.x and v2.2.0. Files encrypted with v2.1.x cannot be decrypted with v2.2.0 and will throw a `VERSION_MISMATCH` or `INVALID_HEADER` error.
422
+
423
+ ---
424
+
425
+ ## Error Handling
426
+
427
+ All errors thrown by WilcoCrypt are instances of `WilcoCryptError`, which extends `Error` with a `code` property.
428
+
429
+ ```js
430
+ import wilcocrypt from "wilcocrypt";
431
+ const { WilcoCryptError } = wilcocrypt._;
432
+
433
+ try {
434
+ wilcocrypt.decryptData(payload, "wrong-password");
435
+ } catch (err) {
436
+ if (err instanceof WilcoCryptError) {
437
+ console.error(err.code); // e.g. DECRYPTION_FAILED
438
+ console.error(err.message); // human-readable
439
+ }
440
+ }
441
+ ```
442
+
443
+ ### Error Codes
444
+
445
+ | Code | Thrown by | Cause |
446
+ | ------------------------ | --------------------------------------------- | ------------------------------------------------------ |
447
+ | `WEAK_PASSWORD` | All public methods | Password shorter than 6 characters |
448
+ | `INVALID_HEADER` | `decryptData`, `decryptFile`, stream variants | Payload does not start with the WilcoCrypt magic bytes |
449
+ | `VERSION_MISMATCH` | `decryptData`, `decryptFile`, stream variants | Payload version does not match current version |
450
+ | `DECRYPTION_FAILED` | `decryptData`, `decryptFile`, stream variants | Wrong password, tampered data, or corruption |
451
+ | `INVALID_FILE_EXTENSION` | `decryptFile` | File path does not end with `.enc` |
452
+ | `INVALID_KEY` | `_.assertKeyAndIv` | Key is not a 32-byte Buffer |
453
+ | `INVALID_IV` | `_.assertKeyAndIv` | IV is not a 12-byte Buffer |
454
+ | `NO_TTY` | CLI password prompt | stdin is not a TTY |
455
+
456
+ ---
457
+
458
+ ## TypeScript
459
+
460
+ WilcoCrypt ships with `wilcocrypt.d.ts`. No `@types` package needed.
461
+
462
+ ```ts
463
+ import wilcocrypt, {
464
+ WilcoCryptError,
465
+ } from "wilcocrypt";
466
+
467
+ const encrypted: Buffer =
468
+ wilcocrypt.encryptData(
469
+ Buffer.from("hi"),
470
+ "passw0rd",
471
+ );
472
+
473
+ const encryptedAsync: Buffer =
474
+ await wilcocrypt.encryptDataAsync(
475
+ Buffer.from("hi"),
476
+ "passw0rd",
477
+ );
478
+
479
+ // decryptFile overloads
480
+ const buf: Buffer =
481
+ wilcocrypt.decryptFile(
482
+ "file.enc",
483
+ "passw0rd",
484
+ );
485
+
486
+ wilcocrypt.decryptFile(
487
+ "file.enc",
488
+ "passw0rd",
489
+ "output.txt",
490
+ );
491
+
492
+ // async overloads
493
+ const asyncBuf: Buffer =
494
+ await wilcocrypt.decryptFileAsync(
495
+ "file.enc",
496
+ "passw0rd",
497
+ );
498
+
499
+ await wilcocrypt.decryptFileAsync(
500
+ "file.enc",
501
+ "passw0rd",
502
+ "output.txt",
503
+ );
504
+
505
+ // streams
506
+ await wilcocrypt.encryptFileStream(
507
+ "in.txt",
508
+ "in.txt.enc",
509
+ "passw0rd",
510
+ );
511
+
512
+ await wilcocrypt.decryptFileStream(
513
+ "in.txt.enc",
514
+ "out.txt",
515
+ "passw0rd",
516
+ );
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Security Notes
522
+
523
+ - **Key derivation** uses [scrypt](https://nodejs.org/api/crypto.html#cryptoscryptsyncpassword-salt-keylen-options) with a 16-byte random salt generated fresh for every encryption. The same password will produce a different key each time.
524
+ - **Authenticated encryption** via AES-256-GCM means any tampering with the ciphertext or auth tag will cause decryption to fail with `DECRYPTION_FAILED`.
525
+ - **No password is stored** anywhere in the payload. There is no way to recover a lost password.
526
+ - **The `gzip` flag must match** between encryption and decryption. If data was encrypted without compression (`gzip: false`), decryption must also use `gzip: false`.
527
+ - See [SECURITY.md](./SECURITY.md) for the responsible disclosure policy.