k9crypt 1.1.6 → 1.1.8

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
@@ -6,9 +6,13 @@ This is a special encryption algorithm created for K9Crypt.
6
6
 
7
7
  ## Updates
8
8
 
9
- **v1.1.6**
9
+ **v1.1.8**
10
10
 
11
- - Due to issues reported in feedback, it has been updated to be usable only as CommonJS.
11
+ - Added `encryptFile()` and `decryptFile()` methods for large file encryption with progress tracking
12
+ - Added `encryptMany()` and `decryptMany()` methods for batch operations with sequential and parallel processing
13
+ - Introduced compression level control (0-9) for flexible speed/size balance
14
+ - All method names simplified for better user experience
15
+ - Parallel processing support for high-volume data operations
12
16
 
13
17
  ## Installation
14
18
 
@@ -21,6 +25,8 @@ bun add k9crypt
21
25
 
22
26
  ## Usage
23
27
 
28
+ ### Basic Usage
29
+
24
30
  ```javascript
25
31
  const k9crypt = require('k9crypt');
26
32
 
@@ -45,6 +51,85 @@ async function test() {
45
51
  test();
46
52
  ```
47
53
 
54
+ ### Advanced Features
55
+
56
+ #### Compression Level Control
57
+
58
+ ```javascript
59
+ const encryptor = new k9crypt(secretKey, { compressionLevel: 5 });
60
+
61
+ const encrypted = await encryptor.encrypt(plaintext, { compressionLevel: 7 });
62
+ ```
63
+
64
+ #### File Encryption with Progress Tracking
65
+
66
+ ```javascript
67
+ async function encryptBigFile() {
68
+ const largeData = 'Very large data...';
69
+
70
+ const encrypted = await encryptor.encryptFile(largeData, {
71
+ compressionLevel: 6,
72
+ onProgress: (progress) => {
73
+ console.log(`Processed: ${progress.processedBytes} bytes`);
74
+ }
75
+ });
76
+
77
+ const decrypted = await encryptor.decryptFile(encrypted, {
78
+ onProgress: (progress) => {
79
+ console.log(`Decrypted: ${progress.processedBytes} bytes`);
80
+ }
81
+ });
82
+
83
+ return decrypted;
84
+ }
85
+ ```
86
+
87
+ #### Multiple Data Encryption
88
+
89
+ ```javascript
90
+ async function encryptMultipleData() {
91
+ const dataArray = ['data1', 'data2', 'data3', 'data4'];
92
+
93
+ const encrypted = await encryptor.encryptMany(dataArray, {
94
+ compressionLevel: 5,
95
+ onProgress: (progress) => {
96
+ console.log(`Progress: ${progress.percentage}% (${progress.current}/${progress.total})`);
97
+ }
98
+ });
99
+
100
+ const decrypted = await encryptor.decryptMany(encrypted, {
101
+ skipInvalid: true,
102
+ onProgress: (progress) => {
103
+ console.log(`Progress: ${progress.percentage}%`);
104
+ }
105
+ });
106
+
107
+ return decrypted;
108
+ }
109
+ ```
110
+
111
+ #### Parallel Processing
112
+
113
+ ```javascript
114
+ async function encryptManyDataFast() {
115
+ const dataArray = Array(100).fill('sample data');
116
+
117
+ const encrypted = await encryptor.encryptMany(dataArray, {
118
+ parallel: true,
119
+ batchSize: 20,
120
+ compressionLevel: 4
121
+ });
122
+
123
+ const decrypted = await encryptor.decryptMany(encrypted, {
124
+ parallel: true,
125
+ batchSize: 20,
126
+ skipInvalid: false
127
+ });
128
+
129
+ return decrypted;
130
+ }
131
+ ```
132
+
48
133
  ## License
49
134
 
50
135
  This project is licensed under the MIT license.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k9crypt",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "A special encryption algorithm created for K9Crypt.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -18,12 +18,9 @@
18
18
  "author": "K9Crypt Team",
19
19
  "license": "MIT",
20
20
  "dependencies": {
21
- "bcrypt": "^6.0.0",
21
+ "argon2": "^0.44.0",
22
22
  "crypto-js": "^4.2.0",
23
- "lz4": "^0.6.5",
24
- "lzma-native": "^8.0.6",
25
- "node-forge": "^1.3.1",
26
- "xxhash": "^0.3.0"
23
+ "lzma-native": "^8.0.6"
27
24
  },
28
25
  "repository": {
29
26
  "type": "git",
package/src/constants.js CHANGED
@@ -3,8 +3,10 @@ module.exports = {
3
3
  IV_SIZE: 16,
4
4
  KEY_SIZE: 32,
5
5
  TAG_SIZE: 16,
6
- PBKDF2_ITERATIONS: 310000,
7
- HASH_SEED: 0xCAFEBABE,
6
+ PBKDF2_ITERATIONS: 50000,
7
+ HASH_SEED: 0xcafebabe,
8
8
  PEPPER: 'veryLongAndComplexPepperValue123!@#$%^&*()_+[]{}|;:,.<>?',
9
9
  HMAC_KEY: 'veryLongAndComplexHMACKeyValue456!@#$%^&*()_+[]{}|;:,.<>?',
10
- };
10
+ ARGON2_SALT_SIZE: 16,
11
+ ARGON2_HASH_LENGTH: 64
12
+ };
package/src/index.js CHANGED
@@ -3,35 +3,44 @@ const { compress, decompress } = require('./utils/compression');
3
3
  const { deriveKey } = require('./utils/keyDerivation');
4
4
  const { encrypt, decrypt } = require('./utils/encryption');
5
5
  const { hash, verifyHash } = require('./utils/hashing');
6
- const { SALT_SIZE, IV_SIZE, TAG_SIZE } = require('./constants');
6
+ const { encryptFile, decryptFile } = require('./utils/streamCrypto');
7
+ const { encryptMany, decryptMany, encryptManyParallel, decryptManyParallel } = require('./utils/batchOperations');
8
+ const { SALT_SIZE, IV_SIZE, TAG_SIZE, ARGON2_SALT_SIZE, ARGON2_HASH_LENGTH } = require('./constants');
7
9
 
8
10
  class K9crypt {
9
- constructor(secretKey) {
11
+ constructor(secretKey, options = {}) {
10
12
  if (!secretKey) {
11
13
  this.secretKey = crypto.randomBytes(50);
12
14
  this._autoGenerated = true;
13
- } else {
15
+ }
16
+
17
+ if (secretKey) {
14
18
  this._autoGenerated = false;
15
19
  this.secretKey = secretKey;
16
20
  }
21
+
22
+ this.defaultCompressionLevel = options.compressionLevel || 3;
17
23
  }
18
24
 
19
25
  getGenerated() {
20
26
  return this._autoGenerated ? this.secretKey : null;
21
27
  }
22
28
 
23
- async encrypt(plaintext) {
29
+ async encrypt(plaintext, options = {}) {
24
30
  try {
25
- const compressed = await compress(plaintext);
31
+ const compressionLevel = options.compressionLevel || this.defaultCompressionLevel;
32
+ const compressed = await compress(plaintext, compressionLevel);
26
33
  const salt = crypto.randomBytes(SALT_SIZE);
27
34
  const key = await deriveKey(this.secretKey, salt);
28
- const { iv1, iv2, iv3, iv4, iv5, encrypted, tag1 } = encrypt(compressed, key);
35
+ const { iv1, iv2, iv3, iv4, iv5, encrypted, tag1 } = await encrypt(compressed, key);
29
36
  const dataToHash = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1]);
30
- const dataHash = hash(dataToHash);
31
- const result = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1, dataHash]);
37
+ const argon2Salt = crypto.randomBytes(ARGON2_SALT_SIZE);
38
+ const dataHash = await hash(dataToHash, argon2Salt);
39
+ const result = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1, argon2Salt, dataHash]);
40
+
32
41
  return result.toString('base64');
33
42
  } catch (error) {
34
- console.log('Encryption failed');
43
+ throw new Error(`Encryption failed: ${error.message}`);
35
44
  }
36
45
  }
37
46
 
@@ -44,21 +53,97 @@ class K9crypt {
44
53
  const iv3 = data.slice(SALT_SIZE + 2 * IV_SIZE, SALT_SIZE + 3 * IV_SIZE);
45
54
  const iv4 = data.slice(SALT_SIZE + 3 * IV_SIZE, SALT_SIZE + 4 * IV_SIZE);
46
55
  const iv5 = data.slice(SALT_SIZE + 4 * IV_SIZE, SALT_SIZE + 5 * IV_SIZE);
47
- const encrypted = data.slice(SALT_SIZE + 5 * IV_SIZE, -TAG_SIZE - 64);
48
- const tag1 = data.slice(-TAG_SIZE - 64, -64);
49
- const dataHash = data.slice(-64);
50
56
 
51
- const dataToVerify = data.slice(0, -64);
52
- if (!verifyHash(dataToVerify, dataHash)) {
53
- console.log('Data integrity check failed');
57
+ const dataHash = data.slice(-ARGON2_HASH_LENGTH);
58
+ const argon2Salt = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE, -ARGON2_HASH_LENGTH);
59
+ const tag1 = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
60
+ const encrypted = data.slice(SALT_SIZE + 5 * IV_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE);
61
+
62
+ const dataToVerify = data.slice(0, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
63
+
64
+ if (!(await verifyHash(dataToVerify, dataHash, argon2Salt))) {
65
+ throw new Error('Data integrity check failed');
54
66
  }
55
67
 
56
68
  const key = await deriveKey(this.secretKey, salt);
57
- const decrypted = decrypt(encrypted, key, iv1, iv2, iv3, iv4, iv5, tag1);
69
+ const decrypted = await decrypt(encrypted, key, iv1, iv2, iv3, iv4, iv5, tag1);
58
70
  const decompressed = await decompress(decrypted);
71
+
59
72
  return decompressed.toString('utf8');
60
73
  } catch (error) {
61
- console.log('Decryption failed');
74
+ throw new Error(`Decryption failed: ${error.message}`);
75
+ }
76
+ }
77
+
78
+ async encryptFile(plaintext, options = {}) {
79
+ try {
80
+ const compressionLevel = options.compressionLevel || this.defaultCompressionLevel;
81
+ const onProgress = options.onProgress || null;
82
+
83
+ return await encryptFile(plaintext, this.secretKey, {
84
+ compressionLevel,
85
+ onProgress
86
+ });
87
+ } catch (error) {
88
+ throw new Error(`File encryption failed: ${error.message}`);
89
+ }
90
+ }
91
+
92
+ async decryptFile(ciphertext, options = {}) {
93
+ try {
94
+ const onProgress = options.onProgress || null;
95
+
96
+ return await decryptFile(ciphertext, this.secretKey, {
97
+ onProgress
98
+ });
99
+ } catch (error) {
100
+ throw new Error(`File decryption failed: ${error.message}`);
101
+ }
102
+ }
103
+
104
+ async encryptMany(dataArray, options = {}) {
105
+ try {
106
+ const compressionLevel = options.compressionLevel || this.defaultCompressionLevel;
107
+ const onProgress = options.onProgress || null;
108
+ const parallel = options.parallel || false;
109
+ const batchSize = options.batchSize || 10;
110
+
111
+ if (parallel) {
112
+ return await encryptManyParallel(dataArray, this.secretKey, {
113
+ compressionLevel,
114
+ batchSize
115
+ });
116
+ }
117
+
118
+ return await encryptMany(dataArray, this.secretKey, {
119
+ compressionLevel,
120
+ onProgress
121
+ });
122
+ } catch (error) {
123
+ throw new Error(`Multiple encryption failed: ${error.message}`);
124
+ }
125
+ }
126
+
127
+ async decryptMany(ciphertextArray, options = {}) {
128
+ try {
129
+ const onProgress = options.onProgress || null;
130
+ const skipInvalid = options.skipInvalid || false;
131
+ const parallel = options.parallel || false;
132
+ const batchSize = options.batchSize || 10;
133
+
134
+ if (parallel) {
135
+ return await decryptManyParallel(ciphertextArray, this.secretKey, {
136
+ skipInvalid,
137
+ batchSize
138
+ });
139
+ }
140
+
141
+ return await decryptMany(ciphertextArray, this.secretKey, {
142
+ skipInvalid,
143
+ onProgress
144
+ });
145
+ } catch (error) {
146
+ throw new Error(`Multiple decryption failed: ${error.message}`);
62
147
  }
63
148
  }
64
149
  }
@@ -0,0 +1,265 @@
1
+ const crypto = require('crypto');
2
+ const { compress, decompress } = require('./compression');
3
+ const { deriveKey } = require('./keyDerivation');
4
+ const { encrypt, decrypt } = require('./encryption');
5
+ const { hash, verifyHash } = require('./hashing');
6
+ const { SALT_SIZE, IV_SIZE, TAG_SIZE, ARGON2_SALT_SIZE, ARGON2_HASH_LENGTH } = require('../constants');
7
+
8
+ exports.encryptMany = async (dataArray, secretKey, options = {}) => {
9
+ try {
10
+ if (!Array.isArray(dataArray)) {
11
+ throw new Error('Data must be an array');
12
+ }
13
+
14
+ if (dataArray.length === 0) {
15
+ return [];
16
+ }
17
+
18
+ const compressionLevel = options.compressionLevel || 3;
19
+ const onProgress = options.onProgress || null;
20
+ const results = [];
21
+ const totalItems = dataArray.length;
22
+
23
+ for (let i = 0; i < dataArray.length; i++) {
24
+ const item = dataArray[i];
25
+
26
+ if (item === null || item === undefined) {
27
+ results.push(null);
28
+ continue;
29
+ }
30
+
31
+ const compressed = await compress(item, compressionLevel);
32
+ const salt = crypto.randomBytes(SALT_SIZE);
33
+ const key = await deriveKey(secretKey, salt);
34
+ const { iv1, iv2, iv3, iv4, iv5, encrypted, tag1 } = await encrypt(compressed, key);
35
+
36
+ const dataToHash = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1]);
37
+ const argon2Salt = crypto.randomBytes(ARGON2_SALT_SIZE);
38
+ const dataHash = await hash(dataToHash, argon2Salt);
39
+
40
+ const result = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1, argon2Salt, dataHash]);
41
+ results.push(result.toString('base64'));
42
+
43
+ if (onProgress) {
44
+ onProgress({
45
+ current: i + 1,
46
+ total: totalItems,
47
+ percentage: Math.round(((i + 1) / totalItems) * 100)
48
+ });
49
+ }
50
+ }
51
+
52
+ return results;
53
+ } catch (error) {
54
+ throw new Error(`Batch encryption failed: ${error.message}`);
55
+ }
56
+ };
57
+
58
+ exports.decryptMany = async (ciphertextArray, secretKey, options = {}) => {
59
+ try {
60
+ if (!Array.isArray(ciphertextArray)) {
61
+ throw new Error('Data must be an array');
62
+ }
63
+
64
+ if (ciphertextArray.length === 0) {
65
+ return [];
66
+ }
67
+
68
+ const onProgress = options.onProgress || null;
69
+ const skipInvalid = options.skipInvalid || false;
70
+ const results = [];
71
+ const totalItems = ciphertextArray.length;
72
+
73
+ for (let i = 0; i < ciphertextArray.length; i++) {
74
+ const item = ciphertextArray[i];
75
+
76
+ if (item === null || item === undefined) {
77
+ results.push(null);
78
+ continue;
79
+ }
80
+
81
+ try {
82
+ const data = Buffer.from(item, 'base64');
83
+ const salt = data.slice(0, SALT_SIZE);
84
+ const iv1 = data.slice(SALT_SIZE, SALT_SIZE + IV_SIZE);
85
+ const iv2 = data.slice(SALT_SIZE + IV_SIZE, SALT_SIZE + 2 * IV_SIZE);
86
+ const iv3 = data.slice(SALT_SIZE + 2 * IV_SIZE, SALT_SIZE + 3 * IV_SIZE);
87
+ const iv4 = data.slice(SALT_SIZE + 3 * IV_SIZE, SALT_SIZE + 4 * IV_SIZE);
88
+ const iv5 = data.slice(SALT_SIZE + 4 * IV_SIZE, SALT_SIZE + 5 * IV_SIZE);
89
+
90
+ const dataHash = data.slice(-ARGON2_HASH_LENGTH);
91
+ const argon2Salt = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE, -ARGON2_HASH_LENGTH);
92
+ const tag1 = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
93
+ const encrypted = data.slice(SALT_SIZE + 5 * IV_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE);
94
+
95
+ const dataToVerify = data.slice(0, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
96
+
97
+ if (!(await verifyHash(dataToVerify, dataHash, argon2Salt))) {
98
+ if (skipInvalid) {
99
+ results.push(null);
100
+ continue;
101
+ }
102
+
103
+ throw new Error(`Data integrity check failed for item ${i}`);
104
+ }
105
+
106
+ const key = await deriveKey(secretKey, salt);
107
+ const decrypted = await decrypt(encrypted, key, iv1, iv2, iv3, iv4, iv5, tag1);
108
+ const decompressed = await decompress(decrypted);
109
+ results.push(decompressed.toString('utf8'));
110
+ } catch (error) {
111
+ if (skipInvalid) {
112
+ results.push(null);
113
+ }
114
+
115
+ if (!skipInvalid) {
116
+ throw error;
117
+ }
118
+ }
119
+
120
+ if (onProgress) {
121
+ onProgress({
122
+ current: i + 1,
123
+ total: totalItems,
124
+ percentage: Math.round(((i + 1) / totalItems) * 100)
125
+ });
126
+ }
127
+ }
128
+
129
+ return results;
130
+ } catch (error) {
131
+ throw new Error(`Batch decryption failed: ${error.message}`);
132
+ }
133
+ };
134
+
135
+ exports.encryptManyParallel = async (dataArray, secretKey, options = {}) => {
136
+ try {
137
+ if (!Array.isArray(dataArray)) {
138
+ throw new Error('Data must be an array');
139
+ }
140
+
141
+ if (dataArray.length === 0) {
142
+ return [];
143
+ }
144
+
145
+ const compressionLevel = options.compressionLevel || 3;
146
+ const batchSize = options.batchSize || 10;
147
+ const results = new Array(dataArray.length);
148
+ const batches = [];
149
+
150
+ for (let i = 0; i < dataArray.length; i += batchSize) {
151
+ const batch = dataArray.slice(i, Math.min(i + batchSize, dataArray.length));
152
+ const batchPromises = batch.map(async (item, index) => {
153
+ const actualIndex = i + index;
154
+
155
+ if (item === null || item === undefined) {
156
+ return { index: actualIndex, result: null };
157
+ }
158
+
159
+ const compressed = await compress(item, compressionLevel);
160
+ const salt = crypto.randomBytes(SALT_SIZE);
161
+ const key = await deriveKey(secretKey, salt);
162
+ const { iv1, iv2, iv3, iv4, iv5, encrypted, tag1 } = await encrypt(compressed, key);
163
+
164
+ const dataToHash = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1]);
165
+ const argon2Salt = crypto.randomBytes(ARGON2_SALT_SIZE);
166
+ const dataHash = await hash(dataToHash, argon2Salt);
167
+
168
+ const result = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1, argon2Salt, dataHash]);
169
+ return { index: actualIndex, result: result.toString('base64') };
170
+ });
171
+
172
+ batches.push(Promise.all(batchPromises));
173
+ }
174
+
175
+ const batchResults = await Promise.all(batches);
176
+
177
+ for (const batch of batchResults) {
178
+ for (const item of batch) {
179
+ results[item.index] = item.result;
180
+ }
181
+ }
182
+
183
+ return results;
184
+ } catch (error) {
185
+ throw new Error(`Parallel batch encryption failed: ${error.message}`);
186
+ }
187
+ };
188
+
189
+ exports.decryptManyParallel = async (ciphertextArray, secretKey, options = {}) => {
190
+ try {
191
+ if (!Array.isArray(ciphertextArray)) {
192
+ throw new Error('Data must be an array');
193
+ }
194
+
195
+ if (ciphertextArray.length === 0) {
196
+ return [];
197
+ }
198
+
199
+ const batchSize = options.batchSize || 10;
200
+ const skipInvalid = options.skipInvalid || false;
201
+ const results = new Array(ciphertextArray.length);
202
+ const batches = [];
203
+
204
+ for (let i = 0; i < ciphertextArray.length; i += batchSize) {
205
+ const batch = ciphertextArray.slice(i, Math.min(i + batchSize, ciphertextArray.length));
206
+ const batchPromises = batch.map(async (item, index) => {
207
+ const actualIndex = i + index;
208
+
209
+ if (item === null || item === undefined) {
210
+ return { index: actualIndex, result: null };
211
+ }
212
+
213
+ try {
214
+ const data = Buffer.from(item, 'base64');
215
+ const salt = data.slice(0, SALT_SIZE);
216
+ const iv1 = data.slice(SALT_SIZE, SALT_SIZE + IV_SIZE);
217
+ const iv2 = data.slice(SALT_SIZE + IV_SIZE, SALT_SIZE + 2 * IV_SIZE);
218
+ const iv3 = data.slice(SALT_SIZE + 2 * IV_SIZE, SALT_SIZE + 3 * IV_SIZE);
219
+ const iv4 = data.slice(SALT_SIZE + 3 * IV_SIZE, SALT_SIZE + 4 * IV_SIZE);
220
+ const iv5 = data.slice(SALT_SIZE + 4 * IV_SIZE, SALT_SIZE + 5 * IV_SIZE);
221
+
222
+ const dataHash = data.slice(-ARGON2_HASH_LENGTH);
223
+ const argon2Salt = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE, -ARGON2_HASH_LENGTH);
224
+ const tag1 = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
225
+ const encrypted = data.slice(SALT_SIZE + 5 * IV_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE);
226
+
227
+ const dataToVerify = data.slice(0, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
228
+
229
+ if (!(await verifyHash(dataToVerify, dataHash, argon2Salt))) {
230
+ if (skipInvalid) {
231
+ return { index: actualIndex, result: null };
232
+ }
233
+
234
+ throw new Error(`Data integrity check failed for item ${actualIndex}`);
235
+ }
236
+
237
+ const key = await deriveKey(secretKey, salt);
238
+ const decrypted = await decrypt(encrypted, key, iv1, iv2, iv3, iv4, iv5, tag1);
239
+ const decompressed = await decompress(decrypted);
240
+ return { index: actualIndex, result: decompressed.toString('utf8') };
241
+ } catch (error) {
242
+ if (skipInvalid) {
243
+ return { index: actualIndex, result: null };
244
+ }
245
+
246
+ throw error;
247
+ }
248
+ });
249
+
250
+ batches.push(Promise.all(batchPromises));
251
+ }
252
+
253
+ const batchResults = await Promise.all(batches);
254
+
255
+ for (const batch of batchResults) {
256
+ for (const item of batch) {
257
+ results[item.index] = item.result;
258
+ }
259
+ }
260
+
261
+ return results;
262
+ } catch (error) {
263
+ throw new Error(`Parallel batch decryption failed: ${error.message}`);
264
+ }
265
+ };
@@ -1,25 +1,36 @@
1
1
  const zlib = require('zlib');
2
2
  const lzma = require('lzma-native');
3
3
 
4
- exports.compress = async (data) => {
4
+ exports.compress = async (data, compressionLevel = 3) => {
5
5
  try {
6
+ if (compressionLevel < 0 || compressionLevel > 9) {
7
+ throw new Error('Compression level must be between 0 and 9');
8
+ }
9
+
10
+ const brotliQuality = Math.min(Math.max(Math.floor(compressionLevel / 2), 1), 11);
11
+ const lzmaLevel = Math.min(compressionLevel, 9);
12
+
6
13
  const brotliParams = {
7
14
  params: {
8
15
  [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
9
- [zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
16
+ [zlib.constants.BROTLI_PARAM_QUALITY]: brotliQuality,
10
17
  [zlib.constants.BROTLI_PARAM_SIZE_HINT]: Buffer.byteLength(data, 'utf8'),
11
18
  [zlib.constants.BROTLI_PARAM_LGWIN]: 24
12
19
  }
13
20
  };
14
21
 
15
22
  const brotliCompressed = await new Promise((resolve, reject) => {
16
- zlib.brotliCompress(Buffer.from(data, 'utf8'), brotliParams, (err, compressed) => {
17
- if (err) reject(err);
18
- else resolve(compressed);
19
- });
23
+ zlib.brotliCompress(
24
+ Buffer.from(data, 'utf8'),
25
+ brotliParams,
26
+ (err, compressed) => {
27
+ if (err) reject(err);
28
+ if (!err) resolve(compressed);
29
+ }
30
+ );
20
31
  });
21
32
 
22
- const lzmaCompressed = await lzma.compress(brotliCompressed, 9);
33
+ const lzmaCompressed = await lzma.compress(brotliCompressed, lzmaLevel);
23
34
  return lzmaCompressed;
24
35
  } catch (error) {
25
36
  throw new Error(`Compression error: ${error.message}`);
@@ -41,4 +52,4 @@ exports.decompress = async (data) => {
41
52
  } catch (error) {
42
53
  throw new Error(`Decompression error: ${error.message}`);
43
54
  }
44
- };
55
+ };
@@ -1,52 +1,61 @@
1
1
  const crypto = require('crypto');
2
+ const { Readable } = require('stream');
2
3
  const { IV_SIZE } = require('../constants');
3
4
  const { reverseBuffer } = require('./math');
4
5
 
5
6
  exports.encrypt = (data, key) => {
6
- const iv1 = crypto.randomBytes(IV_SIZE);
7
- const cipher1 = crypto.createCipheriv('aes-256-gcm', key, iv1);
8
- let encrypted1 = cipher1.update(data);
9
- encrypted1 = Buffer.concat([encrypted1, cipher1.final()]);
10
- const tag1 = cipher1.getAuthTag();
11
- const iv2 = crypto.randomBytes(IV_SIZE);
12
- const cipher2 = crypto.createCipheriv('aes-256-cbc', key, iv2);
13
- let encrypted2 = cipher2.update(encrypted1);
14
- encrypted2 = Buffer.concat([encrypted2, cipher2.final()]);
15
- const iv3 = crypto.randomBytes(IV_SIZE);
16
- const cipher3 = crypto.createCipheriv('aes-256-cfb', key, iv3);
17
- let encrypted3 = cipher3.update(encrypted2);
18
- encrypted3 = Buffer.concat([encrypted3, cipher3.final()]);
19
- const iv4 = crypto.randomBytes(IV_SIZE);
20
- const cipher4 = crypto.createCipheriv('aes-256-ofb', key, iv4);
21
- let encrypted4 = cipher4.update(encrypted3);
22
- encrypted4 = Buffer.concat([encrypted4, cipher4.final()]);
23
- const iv5 = crypto.randomBytes(IV_SIZE);
24
- const cipher5 = crypto.createCipheriv('aes-256-ctr', key, iv5);
25
- let encrypted5 = cipher5.update(encrypted4);
26
- encrypted5 = Buffer.concat([encrypted5, cipher5.final()]);
27
- const permutedEncrypted = reverseBuffer(encrypted5);
28
-
29
- return { iv1, iv2, iv3, iv4, iv5, encrypted: permutedEncrypted, tag1 };
7
+ return new Promise((resolve, reject) => {
8
+ const iv1 = crypto.randomBytes(IV_SIZE);
9
+ const cipher1 = crypto.createCipheriv('aes-256-gcm', key, iv1);
10
+
11
+ const iv2 = crypto.randomBytes(IV_SIZE);
12
+ const cipher2 = crypto.createCipheriv('aes-256-cbc', key, iv2);
13
+
14
+ const iv3 = crypto.randomBytes(IV_SIZE);
15
+ const cipher3 = crypto.createCipheriv('aes-256-cfb', key, iv3);
16
+
17
+ const iv4 = crypto.randomBytes(IV_SIZE);
18
+ const cipher4 = crypto.createCipheriv('aes-256-ofb', key, iv4);
19
+
20
+ const iv5 = crypto.randomBytes(IV_SIZE);
21
+ const cipher5 = crypto.createCipheriv('aes-256-ctr', key, iv5);
22
+
23
+ const readable = Readable.from(data);
24
+ const chunks = [];
25
+
26
+ const stream = readable.pipe(cipher1).pipe(cipher2).pipe(cipher3).pipe(cipher4).pipe(cipher5);
27
+
28
+ stream.on('data', (chunk) => chunks.push(chunk));
29
+ stream.on('error', (err) => reject(err));
30
+ stream.on('end', () => {
31
+ const encrypted = Buffer.concat(chunks);
32
+ const tag1 = cipher1.getAuthTag();
33
+ const permutedEncrypted = reverseBuffer(encrypted);
34
+ resolve({ iv1, iv2, iv3, iv4, iv5, encrypted: permutedEncrypted, tag1 });
35
+ });
36
+ });
30
37
  };
31
38
 
32
39
  exports.decrypt = (encrypted, key, iv1, iv2, iv3, iv4, iv5, tag1) => {
33
- const originalEncrypted = reverseBuffer(encrypted, true);
34
- const decipher5 = crypto.createDecipheriv('aes-256-ctr', key, iv5);
35
- let decrypted5 = decipher5.update(originalEncrypted);
36
- decrypted5 = Buffer.concat([decrypted5, decipher5.final()]);
37
- const decipher4 = crypto.createDecipheriv('aes-256-ofb', key, iv4);
38
- let decrypted4 = decipher4.update(decrypted5);
39
- decrypted4 = Buffer.concat([decrypted4, decipher4.final()]);
40
- const decipher3 = crypto.createDecipheriv('aes-256-cfb', key, iv3);
41
- let decrypted3 = decipher3.update(decrypted4);
42
- decrypted3 = Buffer.concat([decrypted3, decipher3.final()]);
43
- const decipher2 = crypto.createDecipheriv('aes-256-cbc', key, iv2);
44
- let decrypted2 = decipher2.update(decrypted3);
45
- decrypted2 = Buffer.concat([decrypted2, decipher2.final()]);
46
- const decipher1 = crypto.createDecipheriv('aes-256-gcm', key, iv1);
47
- decipher1.setAuthTag(tag1);
48
- let decrypted1 = decipher1.update(decrypted2);
49
- decrypted1 = Buffer.concat([decrypted1, decipher1.final()]);
50
-
51
- return decrypted1;
40
+ return new Promise((resolve, reject) => {
41
+ const originalEncrypted = reverseBuffer(encrypted);
42
+
43
+ const decipher5 = crypto.createDecipheriv('aes-256-ctr', key, iv5);
44
+ const decipher4 = crypto.createDecipheriv('aes-256-ofb', key, iv4);
45
+ const decipher3 = crypto.createDecipheriv('aes-256-cfb', key, iv3);
46
+ const decipher2 = crypto.createDecipheriv('aes-256-cbc', key, iv2);
47
+ const decipher1 = crypto.createDecipheriv('aes-256-gcm', key, iv1);
48
+ decipher1.setAuthTag(tag1);
49
+
50
+ const readable = Readable.from(originalEncrypted);
51
+ const chunks = [];
52
+
53
+ const stream = readable.pipe(decipher5).pipe(decipher4).pipe(decipher3).pipe(decipher2).pipe(decipher1);
54
+
55
+ stream.on('data', (chunk) => chunks.push(chunk));
56
+ stream.on('error', (err) => reject(err));
57
+ stream.on('end', () => {
58
+ resolve(Buffer.concat(chunks));
59
+ });
60
+ });
52
61
  };
@@ -1,13 +1,42 @@
1
1
  const crypto = require('crypto');
2
- const { HMAC_KEY } = require('../constants');
2
+ const argon2 = require('argon2');
3
+ const { HMAC_KEY, ARGON2_HASH_LENGTH } = require('../constants');
3
4
  const { reverseHash } = require('./math');
4
5
 
5
- exports.hash = (data) => {
6
+ const ARGON2_OPTIONS = {
7
+ timeCost: 1,
8
+ memoryCost: 12288,
9
+ parallelism: 4,
10
+ type: argon2.argon2id,
11
+ hashLength: ARGON2_HASH_LENGTH
12
+ };
13
+
14
+ exports.hash = async (data, salt) => {
6
15
  const hmac = crypto.createHmac('sha512', HMAC_KEY);
7
16
  hmac.update(data);
8
17
  const digest = hmac.digest();
18
+ const reversedSha512Hash = reverseHash(digest);
19
+
20
+ const argon2Hash = await argon2.hash(reversedSha512Hash, {
21
+ ...ARGON2_OPTIONS,
22
+ salt,
23
+ raw: true
24
+ });
9
25
 
10
- return reverseHash(digest);
26
+ return argon2Hash;
11
27
  };
12
28
 
13
- exports.verifyHash = (data, hash) => crypto.timingSafeEqual(exports.hash(data), hash);
29
+ exports.verifyHash = async (data, hash, salt) => {
30
+ const hmac = crypto.createHmac('sha512', HMAC_KEY);
31
+ hmac.update(data);
32
+ const digest = hmac.digest();
33
+ const reversedSha512Hash = reverseHash(digest);
34
+
35
+ const expectedHash = await argon2.hash(reversedSha512Hash, {
36
+ ...ARGON2_OPTIONS,
37
+ salt,
38
+ raw: true
39
+ });
40
+
41
+ return crypto.timingSafeEqual(hash, expectedHash);
42
+ };
package/src/utils/math.js CHANGED
@@ -1,14 +1,11 @@
1
- exports.reverseBuffer = (data, reverse = false) => {
2
- if (reverse) {
3
- return Buffer.from(data.toString('hex').split('').reverse().join(''), 'hex');
4
- }
5
- return Buffer.from(data.toString('hex').split('').reverse().join(''), 'hex');
1
+ exports.reverseBuffer = (data) => {
2
+ return Buffer.from(data).reverse();
6
3
  };
7
4
 
8
5
  exports.reverseHash = (hash) => {
9
- return Buffer.from(hash.toString('hex').split('').reverse().join(''), 'hex');
6
+ return Buffer.from(hash).reverse();
10
7
  };
11
8
 
12
9
  exports.enhanceKey = (key) => {
13
- return Buffer.from(key.toString('hex').split('').reverse().join(''), 'hex');
14
- };
10
+ return Buffer.from(key).reverse();
11
+ };
@@ -0,0 +1,219 @@
1
+ const crypto = require('crypto');
2
+ const { Readable, Transform, pipeline } = require('stream');
3
+ const { compress, decompress } = require('./compression');
4
+ const { deriveKey } = require('./keyDerivation');
5
+ const { hash, verifyHash } = require('./hashing');
6
+ const { reverseBuffer } = require('./math');
7
+ const { SALT_SIZE, IV_SIZE, TAG_SIZE, ARGON2_SALT_SIZE, ARGON2_HASH_LENGTH } = require('../constants');
8
+
9
+ const CHUNK_SIZE = 64 * 1024;
10
+
11
+ exports.encryptStream = async (inputStream, secretKey, onProgress) => {
12
+ return new Promise(async (resolve, reject) => {
13
+ try {
14
+ const salt = crypto.randomBytes(SALT_SIZE);
15
+ const key = await deriveKey(secretKey, salt);
16
+ const iv1 = crypto.randomBytes(IV_SIZE);
17
+ const iv2 = crypto.randomBytes(IV_SIZE);
18
+ const iv3 = crypto.randomBytes(IV_SIZE);
19
+ const iv4 = crypto.randomBytes(IV_SIZE);
20
+ const iv5 = crypto.randomBytes(IV_SIZE);
21
+
22
+ const cipher1 = crypto.createCipheriv('aes-256-gcm', key, iv1);
23
+ const cipher2 = crypto.createCipheriv('aes-256-cbc', key, iv2);
24
+ const cipher3 = crypto.createCipheriv('aes-256-cfb', key, iv3);
25
+ const cipher4 = crypto.createCipheriv('aes-256-ofb', key, iv4);
26
+ const cipher5 = crypto.createCipheriv('aes-256-ctr', key, iv5);
27
+
28
+ const chunks = [];
29
+ let totalBytes = 0;
30
+
31
+ const progressTransform = new Transform({
32
+ transform(chunk, encoding, callback) {
33
+ totalBytes += chunk.length;
34
+
35
+ if (onProgress) {
36
+ onProgress({ processedBytes: totalBytes });
37
+ }
38
+
39
+ this.push(chunk);
40
+ callback();
41
+ }
42
+ });
43
+
44
+ const encryptionPipeline = pipeline(
45
+ inputStream,
46
+ cipher1,
47
+ cipher2,
48
+ cipher3,
49
+ cipher4,
50
+ cipher5,
51
+ progressTransform,
52
+ (err) => {
53
+ if (err) {
54
+ reject(err);
55
+ return;
56
+ }
57
+
58
+ const encrypted = Buffer.concat(chunks);
59
+ const tag1 = cipher1.getAuthTag();
60
+ const permutedEncrypted = reverseBuffer(encrypted);
61
+
62
+ resolve({
63
+ salt,
64
+ iv1,
65
+ iv2,
66
+ iv3,
67
+ iv4,
68
+ iv5,
69
+ encrypted: permutedEncrypted,
70
+ tag1
71
+ });
72
+ }
73
+ );
74
+
75
+ encryptionPipeline.on('data', (chunk) => {
76
+ chunks.push(chunk);
77
+ });
78
+ } catch (error) {
79
+ reject(error);
80
+ }
81
+ });
82
+ };
83
+
84
+ exports.decryptStream = async (encryptedData, secretKey, onProgress) => {
85
+ return new Promise(async (resolve, reject) => {
86
+ try {
87
+ const { salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1 } = encryptedData;
88
+ const key = await deriveKey(secretKey, salt);
89
+ const originalEncrypted = reverseBuffer(encrypted);
90
+
91
+ const decipher5 = crypto.createDecipheriv('aes-256-ctr', key, iv5);
92
+ const decipher4 = crypto.createDecipheriv('aes-256-ofb', key, iv4);
93
+ const decipher3 = crypto.createDecipheriv('aes-256-cfb', key, iv3);
94
+ const decipher2 = crypto.createDecipheriv('aes-256-cbc', key, iv2);
95
+ const decipher1 = crypto.createDecipheriv('aes-256-gcm', key, iv1);
96
+ decipher1.setAuthTag(tag1);
97
+
98
+ const readable = Readable.from(originalEncrypted);
99
+ const chunks = [];
100
+ let totalBytes = 0;
101
+
102
+ const progressTransform = new Transform({
103
+ transform(chunk, encoding, callback) {
104
+ totalBytes += chunk.length;
105
+
106
+ if (onProgress) {
107
+ onProgress({ processedBytes: totalBytes });
108
+ }
109
+
110
+ this.push(chunk);
111
+ callback();
112
+ }
113
+ });
114
+
115
+ const decryptionPipeline = pipeline(
116
+ readable,
117
+ decipher5,
118
+ decipher4,
119
+ decipher3,
120
+ decipher2,
121
+ decipher1,
122
+ progressTransform,
123
+ (err) => {
124
+ if (err) {
125
+ reject(err);
126
+ return;
127
+ }
128
+
129
+ resolve(Buffer.concat(chunks));
130
+ }
131
+ );
132
+
133
+ decryptionPipeline.on('data', (chunk) => {
134
+ chunks.push(chunk);
135
+ });
136
+ } catch (error) {
137
+ reject(error);
138
+ }
139
+ });
140
+ };
141
+
142
+ exports.encryptFile = async (data, secretKey, options = {}) => {
143
+ try {
144
+ const onProgress = options.onProgress || null;
145
+ const compressionLevel = options.compressionLevel || 3;
146
+
147
+ if (!Buffer.isBuffer(data)) {
148
+ data = Buffer.from(data, 'utf8');
149
+ }
150
+
151
+ const compressed = await compress(data, compressionLevel);
152
+ const inputStream = Readable.from(compressed);
153
+
154
+ const encryptionResult = await exports.encryptStream(inputStream, secretKey, onProgress);
155
+ const dataToHash = Buffer.concat([
156
+ encryptionResult.salt,
157
+ encryptionResult.iv1,
158
+ encryptionResult.iv2,
159
+ encryptionResult.iv3,
160
+ encryptionResult.iv4,
161
+ encryptionResult.iv5,
162
+ encryptionResult.encrypted,
163
+ encryptionResult.tag1
164
+ ]);
165
+
166
+ const argon2Salt = crypto.randomBytes(ARGON2_SALT_SIZE);
167
+ const dataHash = await hash(dataToHash, argon2Salt);
168
+
169
+ const result = Buffer.concat([
170
+ encryptionResult.salt,
171
+ encryptionResult.iv1,
172
+ encryptionResult.iv2,
173
+ encryptionResult.iv3,
174
+ encryptionResult.iv4,
175
+ encryptionResult.iv5,
176
+ encryptionResult.encrypted,
177
+ encryptionResult.tag1,
178
+ argon2Salt,
179
+ dataHash
180
+ ]);
181
+
182
+ return result.toString('base64');
183
+ } catch (error) {
184
+ throw new Error(`Stream encryption failed: ${error.message}`);
185
+ }
186
+ };
187
+
188
+ exports.decryptFile = async (ciphertext, secretKey, options = {}) => {
189
+ try {
190
+ const onProgress = options.onProgress || null;
191
+ const data = Buffer.from(ciphertext, 'base64');
192
+
193
+ const salt = data.slice(0, SALT_SIZE);
194
+ const iv1 = data.slice(SALT_SIZE, SALT_SIZE + IV_SIZE);
195
+ const iv2 = data.slice(SALT_SIZE + IV_SIZE, SALT_SIZE + 2 * IV_SIZE);
196
+ const iv3 = data.slice(SALT_SIZE + 2 * IV_SIZE, SALT_SIZE + 3 * IV_SIZE);
197
+ const iv4 = data.slice(SALT_SIZE + 3 * IV_SIZE, SALT_SIZE + 4 * IV_SIZE);
198
+ const iv5 = data.slice(SALT_SIZE + 4 * IV_SIZE, SALT_SIZE + 5 * IV_SIZE);
199
+
200
+ const dataHash = data.slice(-ARGON2_HASH_LENGTH);
201
+ const argon2Salt = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE, -ARGON2_HASH_LENGTH);
202
+ const tag1 = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
203
+ const encrypted = data.slice(SALT_SIZE + 5 * IV_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE);
204
+
205
+ const dataToVerify = data.slice(0, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
206
+
207
+ if (!(await verifyHash(dataToVerify, dataHash, argon2Salt))) {
208
+ throw new Error('Data integrity check failed');
209
+ }
210
+
211
+ const encryptedData = { salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1 };
212
+ const decrypted = await exports.decryptStream(encryptedData, secretKey, onProgress);
213
+ const decompressed = await decompress(decrypted);
214
+
215
+ return decompressed.toString('utf8');
216
+ } catch (error) {
217
+ throw new Error(`Stream decryption failed: ${error.message}`);
218
+ }
219
+ };