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 +87 -2
- package/package.json +3 -6
- package/src/constants.js +5 -3
- package/src/index.js +102 -17
- package/src/utils/batchOperations.js +265 -0
- package/src/utils/compression.js +19 -8
- package/src/utils/encryption.js +52 -43
- package/src/utils/hashing.js +33 -4
- package/src/utils/math.js +5 -8
- package/src/utils/streamCrypto.js +219 -0
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.
|
|
9
|
+
**v1.1.8**
|
|
10
10
|
|
|
11
|
-
-
|
|
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.
|
|
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
|
-
"
|
|
21
|
+
"argon2": "^0.44.0",
|
|
22
22
|
"crypto-js": "^4.2.0",
|
|
23
|
-
"
|
|
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:
|
|
7
|
-
HASH_SEED:
|
|
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 {
|
|
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
|
-
}
|
|
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
|
|
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
|
|
31
|
-
const
|
|
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
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/src/utils/compression.js
CHANGED
|
@@ -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]:
|
|
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(
|
|
17
|
-
|
|
18
|
-
|
|
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,
|
|
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
|
+
};
|
package/src/utils/encryption.js
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
};
|
package/src/utils/hashing.js
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
|
-
const
|
|
2
|
+
const argon2 = require('argon2');
|
|
3
|
+
const { HMAC_KEY, ARGON2_HASH_LENGTH } = require('../constants');
|
|
3
4
|
const { reverseHash } = require('./math');
|
|
4
5
|
|
|
5
|
-
|
|
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
|
|
26
|
+
return argon2Hash;
|
|
11
27
|
};
|
|
12
28
|
|
|
13
|
-
exports.verifyHash = (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
|
|
2
|
-
|
|
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
|
|
6
|
+
return Buffer.from(hash).reverse();
|
|
10
7
|
};
|
|
11
8
|
|
|
12
9
|
exports.enhanceKey = (key) => {
|
|
13
|
-
return Buffer.from(key
|
|
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
|
+
};
|