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/.github/ISSUE_TEMPLATE/bug.md +72 -0
- package/.github/ISSUE_TEMPLATE/config.yml +6 -0
- package/.github/ISSUE_TEMPLATE/feature.yml +38 -0
- package/.github/dependabot.yml +11 -0
- package/CHANGELOG.md +100 -0
- package/DOCS.md +527 -0
- package/README.md +61 -65
- package/SECURITY.md +48 -0
- package/package.json +14 -8
- package/src/cli.js +57 -37
- package/src/wilcocrypt.js +429 -102
- package/types/wilcocrypt.d.ts +309 -24
package/README.md
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# WilcoCrypt
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/prettier/prettier)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> **The `master` branch may be unstable during active development.**
|
|
6
|
+
> For production use, always install from [npm](https://www.npmjs.com/package/wilcocrypt) or use a tagged [GitHub Release](https://github.com/computer-wilco/wilcocrypt/releases).
|
|
7
|
+
|
|
8
|
+
A simple, modern Node.js encryption library and CLI tool. AES-256-GCM, password-based key derivation via scrypt, optional gzip compression, and a streaming API for large files.
|
|
6
9
|
|
|
7
10
|
---
|
|
8
11
|
|
|
9
12
|
## Features
|
|
10
13
|
|
|
11
14
|
- AES-256-GCM authenticated encryption
|
|
12
|
-
-
|
|
15
|
+
- scrypt key derivation with a random salt per encryption
|
|
13
16
|
- Optional gzip compression before encryption
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
17
|
+
- Synchronous and asynchronous APIs
|
|
18
|
+
- Streaming API for large files (`encryptFileStream` / `decryptFileStream`)
|
|
19
|
+
- CLI with interactive password prompt
|
|
20
|
+
- Comprehensive TypeScript definitions with full JSDoc support
|
|
21
|
+
- Prettier code formatting
|
|
19
22
|
|
|
20
23
|
---
|
|
21
24
|
|
|
@@ -25,99 +28,92 @@ It is designed around strong defaults, minimal dependencies, and consistent beha
|
|
|
25
28
|
npm install wilcocrypt
|
|
26
29
|
```
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install -g wilcocrypt
|
|
32
|
-
```
|
|
31
|
+
Requires Node.js 18 or later.
|
|
33
32
|
|
|
34
33
|
---
|
|
35
34
|
|
|
36
|
-
##
|
|
35
|
+
## Quick Start
|
|
37
36
|
|
|
38
37
|
```js
|
|
39
|
-
import wilcocrypt from
|
|
38
|
+
import wilcocrypt from "wilcocrypt";
|
|
40
39
|
|
|
41
|
-
// Encrypt a
|
|
42
|
-
wilcocrypt.
|
|
40
|
+
// Encrypt / decrypt a Buffer
|
|
41
|
+
const encrypted = wilcocrypt.encryptData(Buffer.from("Hello!"), "my-password");
|
|
42
|
+
const decrypted = wilcocrypt.decryptData(encrypted, "my-password");
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
|
|
44
|
+
// Encrypt a file → writes file.txt.enc
|
|
45
|
+
wilcocrypt.encryptFile("file.txt", "my-password");
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// Decrypt to Buffer
|
|
48
|
+
const buf = wilcocrypt.decryptFile("file.txt.enc", "my-password");
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
// Decrypt directly to disk
|
|
51
|
+
wilcocrypt.decryptFile("file.txt.enc", "my-password", "output.txt");
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const data = Buffer.from('Hello world');
|
|
56
|
-
|
|
57
|
-
// Encrypt
|
|
58
|
-
const encrypted = wilcocrypt.encryptData(data, 'password');
|
|
59
|
-
|
|
60
|
-
// Decrypt
|
|
61
|
-
const decrypted = wilcocrypt.decryptData(encrypted, 'password');
|
|
62
|
-
|
|
63
|
-
console.log(decrypted.toString());
|
|
53
|
+
// Stream API (memory-efficient for large files)
|
|
54
|
+
await wilcocrypt.encryptFileStream("big.zip", "big.zip.enc", "my-password");
|
|
55
|
+
await wilcocrypt.decryptFileStream("big.zip.enc", "big.zip", "my-password");
|
|
64
56
|
```
|
|
65
57
|
|
|
66
58
|
---
|
|
67
59
|
|
|
68
|
-
## CLI
|
|
60
|
+
## CLI
|
|
69
61
|
|
|
70
62
|
```bash
|
|
71
63
|
# Encrypt
|
|
72
|
-
wilcocrypt -e
|
|
73
|
-
|
|
64
|
+
wilcocrypt -e secret.txt
|
|
65
|
+
# → prompts for password, writes secret.txt.enc
|
|
66
|
+
|
|
67
|
+
# Decrypt to stdout
|
|
68
|
+
wilcocrypt -d secret.txt.enc
|
|
74
69
|
|
|
75
|
-
# Decrypt
|
|
76
|
-
wilcocrypt -d
|
|
77
|
-
wilcocrypt --decrypt file.txt.enc
|
|
70
|
+
# Decrypt to a file
|
|
71
|
+
wilcocrypt -d secret.txt.enc -o secret.txt
|
|
78
72
|
```
|
|
79
73
|
|
|
80
|
-
|
|
74
|
+
See `wilcocrypt --help` for all options.
|
|
81
75
|
|
|
82
76
|
---
|
|
83
77
|
|
|
84
|
-
##
|
|
78
|
+
## Binary Payload Format
|
|
85
79
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```js
|
|
89
|
-
wilcocrypt._
|
|
80
|
+
```
|
|
81
|
+
[ HEADER (10) ] [ VERSION (dynamic) ] [ salt (16) ] [ iv (12) ] [ ciphertext ] [ authTag (16) ]
|
|
90
82
|
```
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
---
|
|
84
|
+
The auth tag is appended at the end for streaming compatibility. See [DOCS.md](./DOCS.md#binary-payload-format) for the full layout.
|
|
95
85
|
|
|
96
|
-
|
|
86
|
+
> **Note:** The format changed in v2.2.0. Payloads from v2.1.x are not compatible.
|
|
97
87
|
|
|
98
|
-
|
|
88
|
+
---
|
|
99
89
|
|
|
100
|
-
|
|
101
|
-
- authTag (hex)
|
|
102
|
-
- salt (hex)
|
|
103
|
-
- iv (hex)
|
|
104
|
-
- version
|
|
90
|
+
## Error Handling
|
|
105
91
|
|
|
106
|
-
|
|
92
|
+
All errors are instances of `WilcoCryptError` with a machine-readable `code` property.
|
|
107
93
|
|
|
108
|
-
|
|
94
|
+
```js
|
|
95
|
+
import wilcocrypt from "wilcocrypt";
|
|
96
|
+
const { WilcoCryptError } = wilcocrypt._;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
wilcocrypt.decryptData(payload, "wrong");
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (err instanceof WilcoCryptError) {
|
|
102
|
+
console.error(err.code); // e.g. DECRYPTION_FAILED
|
|
103
|
+
console.error(err.message);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
109
107
|
|
|
110
|
-
|
|
111
|
-
- Encrypted data must match the exact version
|
|
108
|
+
Common codes: `WEAK_PASSWORD`, `INVALID_HEADER`, `VERSION_MISMATCH`, `DECRYPTION_FAILED`, `INVALID_FILE_EXTENSION`.
|
|
112
109
|
|
|
113
110
|
---
|
|
114
111
|
|
|
115
|
-
##
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
Full API reference, CLI docs, payload format, TypeScript usage, and security notes: **[DOCS.md](./DOCS.md)**
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
- Losing the password means permanent data loss
|
|
119
|
-
- Do not modify encrypted files manually
|
|
120
|
-
- Compression can be disabled if not needed
|
|
116
|
+
Changelog: **[CHANGELOG.md](./CHANGELOG.md)**
|
|
121
117
|
|
|
122
118
|
---
|
|
123
119
|
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
The following versions of WilcoCrypt are currently supported with security updates:
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
| ------- | ------------------ |
|
|
9
|
+
| 2.2.x | :white_check_mark: |
|
|
10
|
+
| 2.1.x | :x: |
|
|
11
|
+
| 2.0.x | :x: |
|
|
12
|
+
| 1.x | :x: |
|
|
13
|
+
|
|
14
|
+
Only the latest minor version in the 2.x range receives updates. All the versions below that are not supported.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Reporting a Vulnerability
|
|
19
|
+
|
|
20
|
+
If you discover a security vulnerability in WilcoCrypt, please report it responsibly.
|
|
21
|
+
|
|
22
|
+
### How to report
|
|
23
|
+
|
|
24
|
+
- Open a **private security advisory** via the GitHub repository
|
|
25
|
+
|
|
26
|
+
Please include:
|
|
27
|
+
|
|
28
|
+
- A clear description of the issue
|
|
29
|
+
- Steps to reproduce (if possible)
|
|
30
|
+
- Impact assessment (what can go wrong)
|
|
31
|
+
|
|
32
|
+
### What to expect
|
|
33
|
+
|
|
34
|
+
- I will investigate and confirm the issue
|
|
35
|
+
- If accepted, a fix will be developed as soon as possible
|
|
36
|
+
- A patched version will be released and documented
|
|
37
|
+
|
|
38
|
+
### Responsible disclosure
|
|
39
|
+
|
|
40
|
+
Please do **not** publicly disclose the vulnerability until a fix has been released.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Notes
|
|
45
|
+
|
|
46
|
+
- WilcoCrypt is designed with strong cryptographic defaults, but misuse (e.g. weak passwords) can still lead to insecure outcomes
|
|
47
|
+
- Always use strong, unique passwords
|
|
48
|
+
- Security is a shared responsibility between the library and its users
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wilcocrypt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "A encrypting tool",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"encrypting",
|
|
@@ -10,12 +10,16 @@
|
|
|
10
10
|
"author": "computerwilco",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "src/wilcocrypt.js",
|
|
13
|
-
"bin":
|
|
13
|
+
"bin": {
|
|
14
|
+
"wilcocrypt": "src/cli.js"
|
|
15
|
+
},
|
|
14
16
|
"types": "types/wilcocrypt.d.ts",
|
|
15
17
|
"scripts": {
|
|
16
|
-
"test": "
|
|
18
|
+
"test": "node src/cli.js",
|
|
19
|
+
"semistandard": "npx semistandard",
|
|
20
|
+
"lint": "npx semistandard --fix; npx prettier --write .",
|
|
17
21
|
"update": "npx -y Jelmerro/nus",
|
|
18
|
-
"rollup": "rollup -c && node src/scripts/fix-cli-min-import.js && node src/scripts/chmod.js",
|
|
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",
|
|
19
23
|
"sea:blob": "node src/scripts/sea-blob.js",
|
|
20
24
|
"sea:linux-x64": "cp node-binaries/linux-x64/node release/wilcocrypt-linux-x64 && npx postject release/wilcocrypt-linux-x64 NODE_SEA_BLOB sea/wilcocrypt.sea --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 && chmod +x release/wilcocrypt-linux-x64",
|
|
21
25
|
"sea:linux-arm64": "cp node-binaries/linux-arm64/node release/wilcocrypt-linux-arm64 && npx postject release/wilcocrypt-linux-arm64 NODE_SEA_BLOB sea/wilcocrypt.sea --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 && chmod +x release/wilcocrypt-linux-arm64",
|
|
@@ -26,14 +30,16 @@
|
|
|
26
30
|
"clean": "rm -rf dist && rm -rf sea/*.sea && rm -rf release/*"
|
|
27
31
|
},
|
|
28
32
|
"dependencies": {
|
|
29
|
-
"commander": "
|
|
30
|
-
"notepack.io": "3.0.1"
|
|
33
|
+
"commander": "15.0.0"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
|
33
|
-
"@rollup/plugin-commonjs": "29.0.
|
|
36
|
+
"@rollup/plugin-commonjs": "29.0.3",
|
|
34
37
|
"@rollup/plugin-node-resolve": "16.0.3",
|
|
35
38
|
"@rollup/plugin-replace": "6.0.3",
|
|
36
39
|
"@rollup/plugin-terser": "1.0.0",
|
|
37
|
-
"
|
|
40
|
+
"@types/node": "25.9.2",
|
|
41
|
+
"prettier": "3.8.3",
|
|
42
|
+
"rollup": "4.61.1",
|
|
43
|
+
"semistandard": "17.0.0"
|
|
38
44
|
}
|
|
39
45
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,61 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import wilcocrypt from "./wilcocrypt.js";
|
|
3
4
|
|
|
4
5
|
/* =========================
|
|
5
6
|
Helpers
|
|
6
7
|
========================= */
|
|
7
8
|
|
|
8
|
-
function promptPassword(promptText =
|
|
9
|
+
function promptPassword(promptText = "Password: ") {
|
|
9
10
|
return new Promise((resolve) => {
|
|
10
11
|
const stdin = process.stdin;
|
|
11
12
|
const stdout = process.stdout;
|
|
12
13
|
|
|
13
14
|
if (!stdin.isTTY) {
|
|
14
15
|
throw new wilcocrypt._.WilcoCryptError(
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
"Password prompt requires a TTY",
|
|
17
|
+
"NO_TTY",
|
|
17
18
|
);
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
+
|
|
20
21
|
stdout.write(promptText);
|
|
21
22
|
|
|
22
|
-
let password =
|
|
23
|
+
let password = "";
|
|
23
24
|
|
|
24
25
|
stdin.setRawMode(true);
|
|
25
26
|
stdin.resume();
|
|
26
|
-
stdin.setEncoding(
|
|
27
|
+
stdin.setEncoding("utf8");
|
|
27
28
|
|
|
28
29
|
function onData(char) {
|
|
29
|
-
if (char ===
|
|
30
|
-
stdout.write(
|
|
30
|
+
if (char === "\r" || char === "\n") {
|
|
31
|
+
stdout.write("\n");
|
|
31
32
|
stdin.setRawMode(false);
|
|
32
33
|
stdin.pause();
|
|
33
|
-
stdin.removeListener(
|
|
34
|
+
stdin.removeListener("data", onData);
|
|
34
35
|
resolve(password);
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
if (char ===
|
|
39
|
-
stdout.write(
|
|
39
|
+
if (char === "\u0003") {
|
|
40
|
+
stdout.write("\n");
|
|
40
41
|
stdin.setRawMode(false);
|
|
41
42
|
stdin.pause();
|
|
42
|
-
stdin.removeListener(
|
|
43
|
+
stdin.removeListener("data", onData);
|
|
43
44
|
process.exit(1);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
if (char ===
|
|
47
|
+
if (char === "\u007f" || char === "\b") {
|
|
47
48
|
if (password.length > 0) {
|
|
48
49
|
password = password.slice(0, -1);
|
|
49
|
-
stdout.write(
|
|
50
|
+
stdout.write("\b \b");
|
|
50
51
|
}
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
password += char;
|
|
55
|
-
stdout.write(
|
|
56
|
+
stdout.write("*");
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
stdin.on(
|
|
59
|
+
stdin.on("data", onData);
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -66,14 +67,22 @@ function promptPassword(promptText = 'Password: ') {
|
|
|
66
67
|
const program = new Command();
|
|
67
68
|
|
|
68
69
|
program
|
|
69
|
-
.name(
|
|
70
|
-
.description(
|
|
71
|
-
.version(wilcocrypt._.VERSION,
|
|
72
|
-
|
|
73
|
-
.option(
|
|
74
|
-
.option(
|
|
75
|
-
|
|
76
|
-
|
|
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");
|
|
77
86
|
|
|
78
87
|
program.parse(process.argv);
|
|
79
88
|
|
|
@@ -83,18 +92,24 @@ const options = program.opts();
|
|
|
83
92
|
Validation
|
|
84
93
|
========================= */
|
|
85
94
|
|
|
86
|
-
const actions = [
|
|
87
|
-
options.encrypt,
|
|
88
|
-
options.decrypt,
|
|
89
|
-
options.unpack
|
|
90
|
-
].filter(Boolean);
|
|
95
|
+
const actions = [options.encrypt, options.decrypt].filter(Boolean);
|
|
91
96
|
|
|
92
97
|
if (actions.length === 0) {
|
|
93
98
|
program.help();
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
if (actions.length > 1) {
|
|
97
|
-
console.error(
|
|
102
|
+
console.error("error: please specify only one action (-e or -d)");
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (options.output && options.stdout) {
|
|
107
|
+
console.error("error: --output and --stdout are mutually exclusive");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (options.output && options.encrypt) {
|
|
112
|
+
console.error("error: --output is only supported for decryption");
|
|
98
113
|
process.exit(1);
|
|
99
114
|
}
|
|
100
115
|
|
|
@@ -105,17 +120,22 @@ if (actions.length > 1) {
|
|
|
105
120
|
(async () => {
|
|
106
121
|
try {
|
|
107
122
|
if (options.encrypt) {
|
|
108
|
-
const password = await promptPassword(
|
|
123
|
+
const password = await promptPassword("Encryption password: ");
|
|
109
124
|
wilcocrypt.encryptFile(options.encrypt, password);
|
|
110
125
|
console.log(`Encrypted: ${options.encrypt}.enc`);
|
|
111
126
|
return;
|
|
112
127
|
}
|
|
113
128
|
|
|
114
129
|
if (options.decrypt) {
|
|
115
|
-
const password = await promptPassword(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
const password = await promptPassword("Decryption password: ");
|
|
131
|
+
|
|
132
|
+
if (options.output) {
|
|
133
|
+
wilcocrypt.decryptFile(options.decrypt, password, options.output);
|
|
134
|
+
console.log(`Decrypted: ${options.output}`);
|
|
135
|
+
} else {
|
|
136
|
+
const result = wilcocrypt.decryptFile(options.decrypt, password);
|
|
137
|
+
process.stdout.write(result);
|
|
138
|
+
}
|
|
119
139
|
}
|
|
120
140
|
} catch (err) {
|
|
121
141
|
console.error(`error: ${err.message}`);
|