spd-lib 1.0.0
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/index.js +126 -0
- package/package.json +30 -0
- package/readme.md +46 -0
package/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const zlib = require('zlib');
|
|
3
|
+
const sodium = require('libsodium-wrappers');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
class SPD {
|
|
7
|
+
constructor(userKey) {
|
|
8
|
+
this.data = [];
|
|
9
|
+
this.keyPair = sodium.crypto_box_keypair(); // Generate a key pair for encryption/decryption
|
|
10
|
+
this.userKey = userKey; // Symmetric key provided by the user
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async addData(name, data) {
|
|
14
|
+
if (!name || typeof name !== 'string' || !name.trim() || !data || typeof data !== 'string' || !data.trim()) {
|
|
15
|
+
throw new Error('Invalid name or data. Both must be non-empty strings.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await sodium.ready;
|
|
19
|
+
const dat = Buffer.from(data);
|
|
20
|
+
const compressedData = zlib.deflateSync(dat);
|
|
21
|
+
const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
|
|
22
|
+
const encryptedData = sodium.crypto_secretbox_easy(compressedData, nonce, this.userKey);
|
|
23
|
+
const hash = crypto.createHash('sha256').update(encryptedData).digest('hex');
|
|
24
|
+
this.data.push({ dataName: name, nonce: Array.from(nonce), data: Array.from(encryptedData), hash });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
saveToFile(outputPath, salt) {
|
|
28
|
+
if (!outputPath || typeof outputPath !== 'string' || !outputPath.trim() || !salt || !(salt instanceof Uint8Array) || salt.length !== 16) {
|
|
29
|
+
throw new Error('Invalid output path or salt.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const keyPair = {
|
|
33
|
+
publicKey: Array.from(this.keyPair.publicKey),
|
|
34
|
+
privateKey: Array.from(this.keyPair.privateKey)
|
|
35
|
+
};
|
|
36
|
+
const spdData = JSON.stringify({ keyPair, data: this.data, salt: Array.from(salt) });
|
|
37
|
+
const compressedSpdData = zlib.deflateSync(spdData);
|
|
38
|
+
fs.writeFileSync(outputPath, compressedSpdData, { mode: 0o600 });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
static async loadFromFile(spdPath, passcode) {
|
|
43
|
+
if (!spdPath || typeof spdPath !== 'string' || !spdPath.trim() || !passcode || typeof passcode !== 'string' || !passcode.trim()) {
|
|
44
|
+
throw new Error('Invalid SPD path or passcode.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await sodium.ready;
|
|
48
|
+
const compressedSpdData = fs.readFileSync(spdPath);
|
|
49
|
+
const spdData = zlib.inflateSync(compressedSpdData).toString('utf8');
|
|
50
|
+
const { keyPair, data, salt } = JSON.parse(spdData);
|
|
51
|
+
|
|
52
|
+
const { pqcKey } = await new SPD().convertPasscodeToPQCKeySalted(passcode, new Uint8Array(salt));
|
|
53
|
+
const pbk = pqcKey.publicKey;
|
|
54
|
+
const spd = new SPD(pbk);
|
|
55
|
+
spd.keyPair = {
|
|
56
|
+
publicKey: Buffer.from(keyPair.publicKey),
|
|
57
|
+
privateKey: Buffer.from(keyPair.privateKey)
|
|
58
|
+
};
|
|
59
|
+
spd.data = data.map(dat => ({
|
|
60
|
+
dataName: dat.dataName,
|
|
61
|
+
nonce: Buffer.from(dat.nonce),
|
|
62
|
+
data: Buffer.from(dat.data),
|
|
63
|
+
hash: dat.hash
|
|
64
|
+
}));
|
|
65
|
+
spd.data.forEach(dat => {
|
|
66
|
+
const calculatedHash = crypto.createHash('sha256').update(Buffer.from(dat.data)).digest('hex');
|
|
67
|
+
if (calculatedHash !== dat.hash) {
|
|
68
|
+
throw new Error(`Data integrity check failed for ${dat.dataName}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return spd;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async extractFiles() {
|
|
75
|
+
await sodium.ready;
|
|
76
|
+
let extractedFiles = {};
|
|
77
|
+
this.data.forEach(dat => {
|
|
78
|
+
const decryptedData = sodium.crypto_secretbox_open_easy(dat.data, dat.nonce, this.userKey);
|
|
79
|
+
const decompressedData = zlib.inflateSync(decryptedData);
|
|
80
|
+
extractedFiles[dat.dataName] = decompressedData.toString('utf8');
|
|
81
|
+
});
|
|
82
|
+
return extractedFiles;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
static async derivePBK(passcode, salt) {
|
|
88
|
+
if (!passcode || typeof passcode !== 'string' || !passcode.trim() || !salt || !(salt instanceof Uint8Array) || salt.length !== 16) {
|
|
89
|
+
throw new Error('Invalid passcode or salt.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
crypto.pbkdf2(passcode, salt, 100000, 32, 'sha256', (err, derivedKey) => {
|
|
94
|
+
if (err) {
|
|
95
|
+
reject(err);
|
|
96
|
+
} else {
|
|
97
|
+
resolve({ pbk: derivedKey, salt: salt });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async convertPasscodeToPQCKeySalted(passcode, salt) {
|
|
104
|
+
if (!passcode || typeof passcode !== 'string' || !passcode.trim() || passcode.length < 8 || !salt || !(salt instanceof Uint8Array) || salt.length !== 16) {
|
|
105
|
+
throw new Error('Invalid passcode or salt.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const { pbk } = await SPD.derivePBK(passcode, salt);
|
|
109
|
+
await sodium.ready;
|
|
110
|
+
const keyPair = sodium.crypto_kx_seed_keypair(pbk.slice(0, sodium.crypto_kx_SEEDBYTES));
|
|
111
|
+
return { pqcKey: { publicKey: keyPair.publicKey, privateKey: keyPair.privateKey }, salt };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async convertPasscodeToPQCKey(passcode) {
|
|
115
|
+
if (!passcode || typeof passcode !== 'string' || !passcode.trim() || passcode.length < 8) {
|
|
116
|
+
throw new Error('Invalid passcode.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { pbk, salt } = await SPD.derivePBK(passcode, crypto.getRandomValues(new Uint8Array(16)));
|
|
120
|
+
await sodium.ready;
|
|
121
|
+
const keyPair = sodium.crypto_kx_seed_keypair(pbk.slice(0, sodium.crypto_kx_SEEDBYTES));
|
|
122
|
+
return { pqcKey: { publicKey: keyPair.publicKey, privateKey: keyPair.privateKey }, salt };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default SPD;
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spd-lib",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SPD or Secure Packaged Data is a compress PQC protected file format to sotre sensitive data localy",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"security",
|
|
11
|
+
"encryption",
|
|
12
|
+
"data-storage",
|
|
13
|
+
"persistent",
|
|
14
|
+
"crypto",
|
|
15
|
+
"compression",
|
|
16
|
+
"file",
|
|
17
|
+
"nodejs",
|
|
18
|
+
"libsodium",
|
|
19
|
+
"zlib"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"author": "ALS-OPSS",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"crypto": "^1.0.1",
|
|
26
|
+
"fs": "^0.0.1-security",
|
|
27
|
+
"libsodium-wrappers": "^0.7.13"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {}
|
|
30
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
```markdown
|
|
2
|
+
# Secure Packaged Data (SPD)
|
|
3
|
+
|
|
4
|
+
Secure Packaged Data (SPD) is a Node.js package for securely storing and retrieving sensitive data persistently. It utilizes encryption, hashing, and compression techniques to ensure the confidentiality, integrity, and efficiency of data storage.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
You can install SPD via npm:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install spd-packager
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
const SPD = require('spd-packager');
|
|
18
|
+
|
|
19
|
+
// Example usage
|
|
20
|
+
(async () => {
|
|
21
|
+
await sodium.ready;
|
|
22
|
+
|
|
23
|
+
const passcode = 'your-secure-passcode';
|
|
24
|
+
const { pqcKey, salt } = await new SPD().convertPasscodeToPQCKey(passcode);
|
|
25
|
+
const userKey = pqcKey.publicKey; // For this example, use the public key as the symmetric key
|
|
26
|
+
const spd = new SPD(userKey); // Create a new SPD object
|
|
27
|
+
await spd.addData('settings', '{"theme":"dark"}'); // Add a file to the SPD object
|
|
28
|
+
await spd.addData('tabs', '[{"title":"Home","url":"https://example.com"},{"title":"About","url":"https://example.com/about"}]'); // Add another file to the SPD object
|
|
29
|
+
spd.saveToFile('output.spd', salt); // Save SPD file to disk with the salt
|
|
30
|
+
const loadedSpd = await SPD.loadFromFile('output.spd', passcode); // Load SPD file with the passcode
|
|
31
|
+
const extractedFiles = await loadedSpd.extractFiles(); // Extract files to memory
|
|
32
|
+
console.log(extractedFiles); // Print extracted files to console
|
|
33
|
+
})();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- **Encryption**: Data is encrypted using a symmetric key derived from a user-provided passcode.
|
|
39
|
+
- **Compression**: Data is compressed before encryption to reduce storage size.
|
|
40
|
+
- **Data Integrity**: Hashing techniques ensure the integrity of stored data.
|
|
41
|
+
- **Asynchronous Operations**: Utilizes asynchronous programming for file I/O and cryptographic operations.
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
|
46
|
+
```
|