wilcocrypt 2.1.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 +59 -44
- package/package.json +11 -7
- package/src/cli.js +124 -0
- package/src/wilcocrypt.js +302 -0
- package/types/wilcocrypt.d.ts +72 -0
- package/dist/cli.js +0 -4441
- package/dist/cli.min.cjs +0 -2
- package/dist/cli.min.js +0 -2
- package/dist/wilcocrypt.js +0 -987
- package/dist/wilcocrypt.min.js +0 -1
- package/rollup.config.js +0 -54
- package/sea/sea-config.json +0 -5
package/README.md
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# WilcoCrypt
|
|
2
2
|
|
|
3
|
-
**WilcoCrypt** is a
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
19
|
-
-
|
|
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
|
|
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
|
-
|
|
52
|
+
```js
|
|
53
|
+
import wilcocrypt from 'wilcocrypt';
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
These APIs are considered internal and may change between versions.
|
|
55
|
+
const data = Buffer.from('Hello world');
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
71
|
-
wilcocrypt -e
|
|
72
|
-
wilcocrypt --encrypt
|
|
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
|
-
#
|
|
79
|
-
wilcocrypt
|
|
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
|
-
##
|
|
84
|
+
## Internal API
|
|
85
|
+
|
|
86
|
+
Advanced users can access internal helpers via:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
wilcocrypt._
|
|
90
|
+
```
|
|
87
91
|
|
|
88
|
-
|
|
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
|
-
##
|
|
96
|
+
## Format Overview
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
Encrypted output is stored as a MessagePack-encoded object containing:
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
100
|
+
- payload (ciphertext, hex)
|
|
101
|
+
- authTag (hex)
|
|
102
|
+
- salt (hex)
|
|
103
|
+
- iv (hex)
|
|
104
|
+
- version
|
|
102
105
|
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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.1.
|
|
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": "
|
|
10
|
+
"author": "computerwilco",
|
|
10
11
|
"type": "module",
|
|
11
|
-
"main": "
|
|
12
|
-
"bin": "
|
|
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;
|