k9crypt 1.1.7 → 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,10 +6,13 @@ This is a special encryption algorithm created for K9Crypt.
6
6
 
7
7
  ## Updates
8
8
 
9
- **v1.1.7**
9
+ **v1.1.8**
10
10
 
11
- - The Argon2 hashing system has now been integrated, offering support for both SHA512 and Argon2.
12
- - Encryption performance has been optimized, significantly increasing speed.
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
13
16
 
14
17
  ## Installation
15
18
 
@@ -22,6 +25,8 @@ bun add k9crypt
22
25
 
23
26
  ## Usage
24
27
 
28
+ ### Basic Usage
29
+
25
30
  ```javascript
26
31
  const k9crypt = require('k9crypt');
27
32
 
@@ -46,6 +51,85 @@ async function test() {
46
51
  test();
47
52
  ```
48
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
+
49
133
  ## License
50
134
 
51
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.7",
3
+ "version": "1.1.8",
4
4
  "description": "A special encryption algorithm created for K9Crypt.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -3,36 +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 { encryptFile, decryptFile } = require('./utils/streamCrypto');
7
+ const { encryptMany, decryptMany, encryptManyParallel, decryptManyParallel } = require('./utils/batchOperations');
6
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
35
  const { iv1, iv2, iv3, iv4, iv5, encrypted, tag1 } = await encrypt(compressed, key);
29
- const dataToHash = Buffer.concat([ salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1 ]);
36
+ const dataToHash = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1]);
30
37
  const argon2Salt = crypto.randomBytes(ARGON2_SALT_SIZE);
31
38
  const dataHash = await hash(dataToHash, argon2Salt);
32
- const result = Buffer.concat([ salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1, argon2Salt, dataHash ]);
39
+ const result = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1, argon2Salt, dataHash]);
40
+
33
41
  return result.toString('base64');
34
42
  } catch (error) {
35
- console.log('Encryption failed');
43
+ throw new Error(`Encryption failed: ${error.message}`);
36
44
  }
37
45
  }
38
46
 
@@ -52,16 +60,90 @@ class K9crypt {
52
60
  const encrypted = data.slice(SALT_SIZE + 5 * IV_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE);
53
61
 
54
62
  const dataToVerify = data.slice(0, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
63
+
55
64
  if (!(await verifyHash(dataToVerify, dataHash, argon2Salt))) {
56
- console.log('Data integrity check failed');
65
+ throw new Error('Data integrity check failed');
57
66
  }
58
67
 
59
68
  const key = await deriveKey(this.secretKey, salt);
60
69
  const decrypted = await decrypt(encrypted, key, iv1, iv2, iv3, iv4, iv5, tag1);
61
70
  const decompressed = await decompress(decrypted);
71
+
62
72
  return decompressed.toString('utf8');
63
73
  } catch (error) {
64
- 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}`);
65
147
  }
66
148
  }
67
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,12 +1,19 @@
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]: 4,
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
  }
@@ -18,12 +25,12 @@ exports.compress = async (data) => {
18
25
  brotliParams,
19
26
  (err, compressed) => {
20
27
  if (err) reject(err);
21
- else resolve(compressed);
28
+ if (!err) resolve(compressed);
22
29
  }
23
30
  );
24
31
  });
25
32
 
26
- const lzmaCompressed = await lzma.compress(brotliCompressed, 3);
33
+ const lzmaCompressed = await lzma.compress(brotliCompressed, lzmaLevel);
27
34
  return lzmaCompressed;
28
35
  } catch (error) {
29
36
  throw new Error(`Compression error: ${error.message}`);
@@ -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
+ };