wilcocrypt 2.0.0 → 2.1.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/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # WilcoCrypt
2
2
 
3
- **WilcoCrypt** is a simple and secure file encryption tool for Node.js.
4
- It offers both a clean programmatic API and a practical command-line interface (CLI) for everyday use.
3
+ **WilcoCrypt** is a small, secure, and predictable encryption library for Node.js.
5
4
 
6
- The library focuses on strong defaults, portability, and predictable behavior across environments
7
- (including low-end devices such as Raspberry Pi and Android/Termux).
5
+ It is designed around strong defaults, minimal dependencies, and consistent behavior across environments — from servers to low-end devices like Raspberry Pi.
8
6
 
9
7
  ---
10
8
 
@@ -12,11 +10,12 @@ The library focuses on strong defaults, portability, and predictable behavior ac
12
10
 
13
11
  - AES-256-GCM authenticated encryption
14
12
  - Password-based key derivation using scrypt
15
- - MessagePack + gzip for compact encrypted files
13
+ - Optional gzip compression before encryption
14
+ - Compact binary format using MessagePack
16
15
  - Built-in versioning and compatibility checks
17
16
  - Clear and consistent error handling
18
- - Clean separation between public API and internal helpers
19
- - Command-line interface (CLI) for quick file encryption and decryption
17
+ - Simple, dependency-light design
18
+ - CLI for quick file encryption and decryption
20
19
 
21
20
  ---
22
21
 
@@ -26,7 +25,7 @@ The library focuses on strong defaults, portability, and predictable behavior ac
26
25
  npm install wilcocrypt
27
26
  ```
28
27
 
29
- ### CLI Installation
28
+ ### CLI (global)
30
29
 
31
30
  ```bash
32
31
  npm install -g wilcocrypt
@@ -44,68 +43,84 @@ wilcocrypt.encryptFile('document.txt', 'myStrongPassword');
44
43
 
45
44
  // Decrypt a file
46
45
  const content = wilcocrypt.decryptFile('document.txt.enc', 'myStrongPassword');
46
+
47
47
  console.log(content);
48
48
  ```
49
49
 
50
- ---
50
+ ### Working with Buffers
51
51
 
52
- ## Internal helpers (optional)
52
+ ```js
53
+ import wilcocrypt from 'wilcocrypt';
53
54
 
54
- Advanced users can access internal helpers via `wilcocrypt._`.
55
- These APIs are considered internal and may change between versions.
55
+ const data = Buffer.from('Hello world');
56
56
 
57
- ```js
58
- const iv = Buffer.from('...');
59
- const key = Buffer.from('...');
60
- const encrypted = wilcocrypt._.encryptData(Buffer.from('Hello'), key, iv);
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());
61
64
  ```
62
65
 
63
66
  ---
64
67
 
65
68
  ## CLI Usage
66
69
 
67
- Once installed globally:
68
-
69
70
  ```bash
70
- # Encrypt a file
71
- wilcocrypt -e document.txt
72
- wilcocrypt --encrypt document.txt
73
-
74
- # Decrypt a file
75
- wilcocrypt -d document.txt.enc
76
- wilcocrypt --decrypt document.txt.enc
71
+ # Encrypt
72
+ wilcocrypt -e file.txt
73
+ wilcocrypt --encrypt file.txt
77
74
 
78
- # Internal: unpack raw encrypted envelope
79
- wilcocrypt --unpack document.txt.enc
75
+ # Decrypt
76
+ wilcocrypt -d file.txt.enc
77
+ wilcocrypt --decrypt file.txt.enc
80
78
  ```
81
79
 
82
80
  The CLI will securely prompt for a password (input is masked).
83
81
 
84
82
  ---
85
83
 
86
- ## Version
84
+ ## Internal API
85
+
86
+ Advanced users can access internal helpers via:
87
+
88
+ ```js
89
+ wilcocrypt._
90
+ ```
87
91
 
88
- - Current version: **2.0.0**
89
- - Version 1.x is deprecated and should not be used
92
+ These APIs are **not stable** and may change between versions.
90
93
 
91
94
  ---
92
95
 
93
- ## License
96
+ ## Format Overview
94
97
 
95
- WilcoCrypt is released under the **GPL-3.0-only** license.
98
+ Encrypted output is stored as a MessagePack-encoded object containing:
96
99
 
97
- You are free to:
98
- - Use the software for any purpose
99
- - Study how it works and modify it
100
- - Redistribute the software
101
- - Distribute modified versions
100
+ - payload (ciphertext, hex)
101
+ - authTag (hex)
102
+ - salt (hex)
103
+ - iv (hex)
104
+ - version
102
105
 
103
- Under the condition that:
104
- - Any distributed derivative work is also licensed under GPL-3.0-only
105
- - The source code remains available to users
106
+ ---
106
107
 
107
- This software is provided **without any warranty**.
108
- Use it at your own risk.
108
+ ## Version
109
+
110
+ - Current version: **2.1.1**
111
+ - Encrypted data must match the exact version
112
+
113
+ ---
114
+
115
+ ## Security Notes
116
+
117
+ - Always use strong, unique passwords
118
+ - Losing the password means permanent data loss
119
+ - Do not modify encrypted files manually
120
+ - Compression can be disabled if not needed
121
+
122
+ ---
123
+
124
+ ## License
109
125
 
110
- For full license text, see:
111
- https://www.gnu.org/licenses/gpl-3.0.html
126
+ Licensed under **GPL-3.0-only**.
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "wilcocrypt",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "A encrypting tool",
5
5
  "keywords": [
6
- "encrypting"
6
+ "encrypting",
7
+ "crypto"
7
8
  ],
8
9
  "license": "GPL-3.0-only",
9
- "author": "Wilco Joosen",
10
+ "author": "computerwilco",
10
11
  "type": "module",
11
- "main": "dist/wilcocrypt.min.js",
12
- "bin": "dist/cli.min.js",
12
+ "main": "src/wilcocrypt.js",
13
+ "bin": "src/cli.js",
14
+ "types": "types/wilcocrypt.d.ts",
13
15
  "scripts": {
14
16
  "test": "echo \"Error: no test specified\" && exit 1",
15
17
  "update": "npx -y Jelmerro/nus",
@@ -23,13 +25,15 @@
23
25
  "build": "npm run rollup && npm run sea:blob && npm run sea:linux-x64 && npm run sea:linux-arm64 && npm run sea:windows-x64 && npm run sea:macos-x64 && npm run sea:macos-arm64",
24
26
  "clean": "rm -rf dist && rm -rf sea/*.sea && rm -rf release/*"
25
27
  },
28
+ "dependencies": {
29
+ "commander": "14.0.3",
30
+ "notepack.io": "3.0.1"
31
+ },
26
32
  "devDependencies": {
27
33
  "@rollup/plugin-commonjs": "29.0.2",
28
34
  "@rollup/plugin-node-resolve": "16.0.3",
29
35
  "@rollup/plugin-replace": "6.0.3",
30
36
  "@rollup/plugin-terser": "1.0.0",
31
- "commander": "14.0.3",
32
- "notepack.io": "3.0.1",
33
37
  "rollup": "4.60.2"
34
38
  }
35
39
  }
package/src/cli.js ADDED
@@ -0,0 +1,124 @@
1
+ import { Command } from 'commander';
2
+ import wilcocrypt from './wilcocrypt.js';
3
+
4
+ /* =========================
5
+ Helpers
6
+ ========================= */
7
+
8
+ function promptPassword(promptText = 'Password: ') {
9
+ return new Promise((resolve) => {
10
+ const stdin = process.stdin;
11
+ const stdout = process.stdout;
12
+
13
+ if (!stdin.isTTY) {
14
+ throw new wilcocrypt._.WilcoCryptError(
15
+ 'Password prompt requires a TTY',
16
+ 'NO_TTY'
17
+ );
18
+ }
19
+
20
+ stdout.write(promptText);
21
+
22
+ let password = '';
23
+
24
+ stdin.setRawMode(true);
25
+ stdin.resume();
26
+ stdin.setEncoding('utf8');
27
+
28
+ function onData(char) {
29
+ if (char === '\r' || char === '\n') {
30
+ stdout.write('\n');
31
+ stdin.setRawMode(false);
32
+ stdin.pause();
33
+ stdin.removeListener('data', onData);
34
+ resolve(password);
35
+ return;
36
+ }
37
+
38
+ if (char === '\u0003') {
39
+ stdout.write('\n');
40
+ stdin.setRawMode(false);
41
+ stdin.pause();
42
+ stdin.removeListener('data', onData);
43
+ process.exit(1);
44
+ }
45
+
46
+ if (char === '\u007f' || char === '\b') {
47
+ if (password.length > 0) {
48
+ password = password.slice(0, -1);
49
+ stdout.write('\b \b');
50
+ }
51
+ return;
52
+ }
53
+
54
+ password += char;
55
+ stdout.write('*');
56
+ }
57
+
58
+ stdin.on('data', onData);
59
+ });
60
+ }
61
+
62
+ /* =========================
63
+ CLI setup
64
+ ========================= */
65
+
66
+ const program = new Command();
67
+
68
+ program
69
+ .name('wilcocrypt')
70
+ .description('File encryption tool')
71
+ .version(wilcocrypt._.VERSION, '--version', 'Show version')
72
+
73
+ .option('-e, --encrypt <file>', 'Encrypt file')
74
+ .option('-d, --decrypt <file>', 'Decrypt file')
75
+
76
+ .helpOption('-h, --help', 'Display help');
77
+
78
+ program.parse(process.argv);
79
+
80
+ const options = program.opts();
81
+
82
+ /* =========================
83
+ Validation
84
+ ========================= */
85
+
86
+ const actions = [
87
+ options.encrypt,
88
+ options.decrypt,
89
+ options.unpack
90
+ ].filter(Boolean);
91
+
92
+ if (actions.length === 0) {
93
+ program.help();
94
+ }
95
+
96
+ if (actions.length > 1) {
97
+ console.error('error: please specify only one action (-e, -d or --unpack)');
98
+ process.exit(1);
99
+ }
100
+
101
+ /* =========================
102
+ Actions
103
+ ========================= */
104
+
105
+ (async () => {
106
+ try {
107
+ if (options.encrypt) {
108
+ const password = await promptPassword('Encryption password: ');
109
+ wilcocrypt.encryptFile(options.encrypt, password);
110
+ console.log(`Encrypted: ${options.encrypt}.enc`);
111
+ return;
112
+ }
113
+
114
+ if (options.decrypt) {
115
+ const password = await promptPassword('Decryption password: ');
116
+ const result = wilcocrypt.decryptFile(options.decrypt, password);
117
+ process.stdout.write(result);
118
+ return;
119
+ }
120
+ } catch (err) {
121
+ console.error(`error: ${err.message}`);
122
+ process.exit(1);
123
+ }
124
+ })();
@@ -0,0 +1,302 @@
1
+ import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from 'crypto';
2
+ import { encode as msgpack_encode, decode as msgpack_decode } from 'notepack.io';
3
+ import { gzipSync, gunzipSync } from 'zlib';
4
+ import { readFileSync, writeFileSync } from 'fs';
5
+
6
+ /**
7
+ * Main WilcoCrypt namespace.
8
+ */
9
+ const wilcocrypt = {};
10
+
11
+ /**
12
+ * Internal WilcoCrypt utilities and constants.
13
+ */
14
+ wilcocrypt._ = {};
15
+
16
+ /* =========================
17
+ Custom Error
18
+ ========================= */
19
+
20
+ /**
21
+ * Custom error class for all WilcoCrypt-specific errors.
22
+ */
23
+ class WilcoCryptError extends Error {
24
+ /**
25
+ * @param {string} message - Human-readable error message
26
+ * @param {string} [code=WILCOCRYPT_ERROR] - Machine-readable error code
27
+ */
28
+ constructor(message, code = 'WILCOCRYPT_ERROR') {
29
+ super(message);
30
+ this.name = 'WilcoCryptError';
31
+ this.code = code;
32
+
33
+ if (Error.captureStackTrace) {
34
+ Error.captureStackTrace(this, WilcoCryptError);
35
+ }
36
+ }
37
+ }
38
+
39
+ wilcocrypt._.WilcoCryptError = WilcoCryptError;
40
+
41
+ /* =========================
42
+ Internal constants
43
+ ========================= */
44
+
45
+ /**
46
+ * Internal WilcoCrypt version.
47
+ * Must match exactly during decryption.
48
+ * @type {string}
49
+ */
50
+ wilcocrypt._.VERSION = '2.1.1';
51
+
52
+ /**
53
+ * Minimum allowed password length.
54
+ * @type {number}
55
+ */
56
+ wilcocrypt._.MIN_PASSWORD_LENGTH = 6;
57
+
58
+ /* =========================
59
+ Internal helpers
60
+ ========================= */
61
+
62
+ /**
63
+ * Validates AES-256-GCM key and IV.
64
+ *
65
+ * @param {Buffer} key
66
+ * @param {Buffer} iv
67
+ * @throws {WilcoCryptError}
68
+ */
69
+ wilcocrypt._.assertKeyAndIv = function (key, iv) {
70
+ if (!Buffer.isBuffer(key) || key.length !== 32) {
71
+ throw new WilcoCryptError(
72
+ 'Invalid encryption key (expected 32-byte Buffer)',
73
+ 'INVALID_KEY'
74
+ );
75
+ }
76
+
77
+ if (!Buffer.isBuffer(iv) || iv.length !== 12) {
78
+ throw new WilcoCryptError(
79
+ 'Invalid IV (expected 12-byte Buffer for GCM)',
80
+ 'INVALID_IV'
81
+ );
82
+ }
83
+ };
84
+
85
+ /**
86
+ * Validates password strength.
87
+ *
88
+ * @param {string} password
89
+ * @throws {WilcoCryptError}
90
+ */
91
+ wilcocrypt._.assertPassword = function (password) {
92
+ if (typeof password !== 'string' || password.length < wilcocrypt._.MIN_PASSWORD_LENGTH) {
93
+ throw new WilcoCryptError(
94
+ `Password must be at least ${wilcocrypt._.MIN_PASSWORD_LENGTH} characters`,
95
+ 'WEAK_PASSWORD'
96
+ );
97
+ }
98
+ };
99
+
100
+ /**
101
+ * Constant-time buffer comparison.
102
+ * Reserved for future extensions.
103
+ *
104
+ * @param {Buffer} a
105
+ * @param {Buffer} b
106
+ * @returns {boolean}
107
+ */
108
+ wilcocrypt._.constantTimeEqual = function (a, b) {
109
+ if (a.length !== b.length) return false;
110
+
111
+ let result = 0;
112
+ for (let i = 0; i < a.length; i++) {
113
+ result |= a[i] ^ b[i];
114
+ }
115
+ return result === 0;
116
+ };
117
+
118
+ /* =========================
119
+ Crypto layer (internal)
120
+ ========================= */
121
+
122
+ /**
123
+ * Encrypts raw data using AES-256-GCM.
124
+ *
125
+ * @param {Buffer} plainData
126
+ * @param {Buffer} key
127
+ * @param {Buffer} iv
128
+ * @returns {{ciphertext: Buffer, authTag: Buffer}}
129
+ */
130
+ wilcocrypt._.encryptData = function (plainData, key, iv) {
131
+ wilcocrypt._.assertKeyAndIv(key, iv);
132
+
133
+ const cipher = createCipheriv('aes-256-gcm', key, iv);
134
+ const encrypted = Buffer.concat([
135
+ cipher.update(plainData),
136
+ cipher.final()
137
+ ]);
138
+
139
+ return {
140
+ ciphertext: encrypted,
141
+ authTag: cipher.getAuthTag()
142
+ };
143
+ };
144
+
145
+ /**
146
+ * Decrypts AES-256-GCM encrypted data.
147
+ *
148
+ * @param {string} cipherHex
149
+ * @param {string} authTagHex
150
+ * @param {Buffer} key
151
+ * @param {Buffer} iv
152
+ * @returns {Buffer}
153
+ */
154
+ wilcocrypt._.decryptData = function (cipherHex, authTagHex, key, iv) {
155
+ wilcocrypt._.assertKeyAndIv(key, iv);
156
+
157
+ try {
158
+ const decipher = createDecipheriv('aes-256-gcm', key, iv);
159
+ decipher.setAuthTag(Buffer.from(authTagHex, 'hex'));
160
+
161
+ return Buffer.concat([
162
+ decipher.update(Buffer.from(cipherHex, 'hex')),
163
+ decipher.final()
164
+ ]);
165
+ } catch {
166
+ throw new WilcoCryptError(
167
+ 'Decryption failed (invalid password, corrupted data, or tampered file)',
168
+ 'DECRYPTION_FAILED'
169
+ );
170
+ }
171
+ };
172
+
173
+ /* =========================
174
+ Public API
175
+ ========================= */
176
+
177
+ /**
178
+ * Encrypts data using password-based AES-256-GCM.
179
+ *
180
+ * @param {Buffer} plaindata - Raw data to encrypt
181
+ * @param {string} password - Password used for key derivation
182
+ * @param {boolean} [gzip=true] - Whether to compress data before encryption
183
+ * @returns {Buffer} MessagePack-encoded encrypted payload
184
+ * @throws {WilcoCryptError} If password is invalid
185
+ */
186
+ wilcocrypt.encryptData = function (plaindata, password, gzip = true) {
187
+ wilcocrypt._.assertPassword(password);
188
+
189
+ let gzipData;
190
+ if (gzip) {
191
+ gzipData = gzipSync(plaindata);
192
+ } else {
193
+ gzipData = plaindata;
194
+ }
195
+
196
+ const iv = randomBytes(12);
197
+ const salt = randomBytes(16);
198
+
199
+ const key = scryptSync(password, salt, 32);
200
+
201
+ const { ciphertext, authTag } = wilcocrypt._.encryptData(gzipData, key, iv);
202
+
203
+ const envelope = {
204
+ payload: ciphertext.toString('hex'),
205
+ authTag: authTag.toString('hex'),
206
+ salt: salt.toString('hex'),
207
+ iv: iv.toString('hex'),
208
+ version: wilcocrypt._.VERSION
209
+ };
210
+
211
+ return msgpack_encode(envelope);
212
+ };
213
+
214
+ /**
215
+ * Decrypts encrypted data using password-based AES-256-GCM.
216
+ *
217
+ * @param {Buffer} encryptedData - MessagePack-encoded encrypted payload
218
+ * @param {string} password - Password used for decryption
219
+ * @param {boolean} [gzip=true] - Whether to decompress after decryption
220
+ * @returns {Buffer} Decrypted raw data
221
+ * @throws {WilcoCryptError} On invalid format, wrong password, version mismatch, or decompression failure
222
+ */
223
+ wilcocrypt.decryptData = function (encryptedData, password, gzip = true) {
224
+ wilcocrypt._.assertPassword(password);
225
+
226
+ let envelope;
227
+ try {
228
+ envelope = msgpack_decode(encryptedData);
229
+ } catch {
230
+ throw new WilcoCryptError(
231
+ 'Invalid encrypted data format (not MessagePack)',
232
+ 'INVALID_FORMAT'
233
+ );
234
+ }
235
+
236
+ if (envelope.version !== wilcocrypt._.VERSION) {
237
+ throw new WilcoCryptError(
238
+ `Version mismatch (expected ${wilcocrypt._.VERSION}, got ${envelope.version})`,
239
+ 'VERSION_MISMATCH'
240
+ );
241
+ }
242
+
243
+ const key = scryptSync(password, Buffer.from(envelope.salt, 'hex'), 32);
244
+
245
+ const decrypted = wilcocrypt._.decryptData(
246
+ envelope.payload,
247
+ envelope.authTag,
248
+ key,
249
+ Buffer.from(envelope.iv, 'hex')
250
+ );
251
+
252
+ try {
253
+ if (gzip) {
254
+ return gunzipSync(decrypted);
255
+ } else {
256
+ return decrypted;
257
+ }
258
+ } catch {
259
+ throw new WilcoCryptError(
260
+ 'Decryption succeeded but decompression failed (data may be corrupted or not compressed)',
261
+ 'DECOMPRESSION_FAILED'
262
+ );
263
+ }
264
+ };
265
+
266
+ /**
267
+ * Encrypts a file and writes the result to `<filePath>.enc`.
268
+ *
269
+ * @param {string} filePath - Path to the file to encrypt
270
+ * @param {string} password - Password used for encryption
271
+ * @param {boolean} [gzip=true] - Whether to compress before encryption
272
+ * @returns {void}
273
+ * @throws {WilcoCryptError} If password is invalid
274
+ */
275
+ wilcocrypt.encryptFile = function (filePath, password, gzip = true) {
276
+ const fileData = readFileSync(filePath);
277
+ const encryptedData = wilcocrypt.encryptData(fileData, password, gzip);
278
+ writeFileSync(`${filePath}.enc`, encryptedData);
279
+ };
280
+
281
+ /**
282
+ * Decrypts an encrypted `.enc` file.
283
+ *
284
+ * @param {string} filePath - Path to the `.enc` file
285
+ * @param {string} password - Password used for decryption
286
+ * @param {boolean} [gzip=true] - Whether to decompress after decryption
287
+ * @returns {Buffer} Decrypted file contents
288
+ * @throws {WilcoCryptError} If file extension is invalid or decryption fails
289
+ */
290
+ wilcocrypt.decryptFile = function (filePath, password, gzip = true) {
291
+ if (!filePath.endsWith('.enc')) {
292
+ throw new WilcoCryptError(
293
+ 'Invalid file extension (expected .enc)',
294
+ 'INVALID_FILE_EXTENSION'
295
+ );
296
+ }
297
+
298
+ const encryptedData = readFileSync(filePath);
299
+ return wilcocrypt.decryptData(encryptedData, password, gzip);
300
+ };
301
+
302
+ export default wilcocrypt;
@@ -0,0 +1,72 @@
1
+ /// <reference types="node" />
2
+
3
+ export class WilcoCryptError extends Error {
4
+ code: string;
5
+ constructor(message: string, code?: string);
6
+ }
7
+
8
+ export interface EncryptResultEnvelope {
9
+ payload: string;
10
+ authTag: string;
11
+ salt: string;
12
+ iv: string;
13
+ version: string;
14
+ }
15
+
16
+ export interface InternalNamespace {
17
+ VERSION: string;
18
+ MIN_PASSWORD_LENGTH: number;
19
+
20
+ WilcoCryptError: typeof WilcoCryptError;
21
+
22
+ assertKeyAndIv(key: Buffer, iv: Buffer): void;
23
+ assertPassword(password: string): void;
24
+ constantTimeEqual(a: Buffer, b: Buffer): boolean;
25
+
26
+ encryptData(
27
+ plainData: Buffer,
28
+ key: Buffer,
29
+ iv: Buffer
30
+ ): {
31
+ ciphertext: Buffer;
32
+ authTag: Buffer;
33
+ };
34
+
35
+ decryptData(
36
+ cipherHex: string,
37
+ authTagHex: string,
38
+ key: Buffer,
39
+ iv: Buffer
40
+ ): Buffer;
41
+ }
42
+
43
+ export interface WilcoCrypt {
44
+ _: InternalNamespace;
45
+
46
+ encryptData(
47
+ plaindata: Buffer,
48
+ password: string,
49
+ gzip?: boolean
50
+ ): Buffer;
51
+
52
+ decryptData(
53
+ encryptedData: Buffer,
54
+ password: string,
55
+ gzip?: boolean
56
+ ): Buffer;
57
+
58
+ encryptFile(
59
+ filePath: string,
60
+ password: string,
61
+ gzip?: boolean
62
+ ): void;
63
+
64
+ decryptFile(
65
+ filePath: string,
66
+ password: string,
67
+ gzip?: boolean
68
+ ): Buffer;
69
+ }
70
+
71
+ declare const wilcocrypt: WilcoCrypt;
72
+ export default wilcocrypt;