spd-lib 1.1.5 → 1.1.7

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.
Files changed (2) hide show
  1. package/index.js +84 -13
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -11,11 +11,27 @@ class SPD {
11
11
  this.keyPair; // Generate a key pair for encryption/decryption
12
12
  this.userKey;
13
13
  this.salt;
14
+ this.signingKeyPair;
14
15
  this.init();
15
16
  }
16
17
  async init() {
17
18
  await sodium.ready;
18
19
  this.keyPair = sodium.crypto_box_keypair()
20
+ this.signingKeyPair = sodium.crypto_sign_keypair();
21
+ }
22
+ static async deriveSigningKeys(passcode, salt) {
23
+ if (!passcode || typeof passcode !== 'string' || passcode.length < 8 ||
24
+ !salt || !(salt instanceof Buffer) || salt.length !== 16) {
25
+ throw new Error('Invalid passcode or salt.');
26
+ }
27
+
28
+ const { pbk } = await SPD.derivePBK(passcode, salt);
29
+ await sodium.ready;
30
+ // Derive separate keys for signing to avoid key reuse
31
+ const signingSeed = pbk.slice(0, sodium.crypto_sign_SEEDBYTES);
32
+ const signingKeyPair = sodium.crypto_sign_seed_keypair(signingSeed);
33
+
34
+ return { signingKeyPair };
19
35
  }
20
36
  async setPassKey(passcode){
21
37
  await sodium.ready;
@@ -24,6 +40,8 @@ class SPD {
24
40
  const userKey = pqcKey.publicKey;
25
41
  this.userKey = userKey;
26
42
  this.salt = salt;
43
+ const derivedKeys = await SPD.deriveSigningKeys(passcode, salt);
44
+ this.signingKeyPair = derivedKeys.signingKeyPair;
27
45
  }
28
46
 
29
47
  async addData(name, data) {
@@ -36,7 +54,8 @@ this.salt = salt;
36
54
  const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
37
55
  const encryptedData = sodium.crypto_secretbox_easy(compressedData, nonce, this.userKey);
38
56
  const hash = crypto.createHash('sha256').update(encryptedData).digest('hex');
39
- this.data.push({ dataName: name, nonce: Array.from(nonce), data: Array.from(encryptedData), hash, dataType: dmap[1] });
57
+ const signature = sodium.crypto_sign_detached(encryptedData, this.signingKeyPair.privateKey);
58
+ this.data.push({ dataName: name, nonce: Array.from(nonce), data: Array.from(encryptedData),signature:Array.from(signature), hash, dataType: dmap[1] });
40
59
  }
41
60
 
42
61
  saveToFile(outputPath) {
@@ -44,7 +63,7 @@ this.salt = salt;
44
63
  throw new Error('Invalid output path or salt.');
45
64
  }
46
65
 
47
- const spdData = JSON.stringify({ data: this.data, salt: Array.from(this.salt) });
66
+ const spdData = JSON.stringify({ data: this.data, salt: Array.from(this.salt),signingPublicKey:Array.from(this.signingKeyPair.publicKey) });
48
67
  const compressedSpdData = zlib.deflateSync(spdData);
49
68
  fs.writeFileSync(outputPath, compressedSpdData, { mode: 0o600 });
50
69
  }
@@ -60,7 +79,7 @@ this.salt = salt;
60
79
  await sodium.ready;
61
80
  const compressedSpdData = fs.readFileSync(spdPath);
62
81
  const spdData = zlib.inflateSync(compressedSpdData).toString('utf8');
63
- const { data, salt } = JSON.parse(spdData);
82
+ const { data, salt, signingPublicKey } = JSON.parse(spdData);
64
83
 
65
84
  const { pqcKey } = await new SPD().convertPasscodeToPQCKeySalted(passcode, new Uint8Array(salt));
66
85
  const pbk = pqcKey.publicKey;
@@ -69,19 +88,34 @@ this.salt = salt;
69
88
  spd.keyPair = {
70
89
  publicKey: pbk.publicKey
71
90
  };
91
+
92
+ spd.signingKeyPair = { publicKey: Uint8Array.from(signingPublicKey) };
72
93
  spd.data = data.map(dat => ({
73
94
  dataName: dat.dataName,
74
95
  nonce: Buffer.from(dat.nonce),
75
96
  data: Buffer.from(dat.data),
97
+ signature: Buffer.from(dat.signature),
76
98
  hash: dat.hash,
77
99
  dataType: dat.dataType
78
100
  }));
79
101
  spd.data.forEach(dat => {
80
- const calculatedHash = crypto.createHash('sha256').update(Buffer.from(dat.data)).digest('hex');
102
+ const encryptedBuffer = Buffer.from(dat.data);
103
+ const signatureBuffer = Buffer.from(dat.signature);
104
+ const signingPublicKeyBuffer = Uint8Array.from(signingPublicKey);
105
+
106
+ const isValid = sodium.crypto_sign_verify_detached(signatureBuffer, encryptedBuffer, signingPublicKeyBuffer);
107
+ if (!isValid) {
108
+ rej(new Error(`Signature verification failed for ${dat.dataName}`));
109
+ return;
110
+ }
111
+
112
+ const calculatedHash = crypto.createHash('sha256').update(encryptedBuffer).digest('hex');
81
113
  if (calculatedHash !== dat.hash) {
82
114
  rej(new Error(`Data integrity check failed for ${dat.dataName}`));
115
+ return;
83
116
  }
84
117
  });
118
+
85
119
  res(spd);
86
120
  }catch{
87
121
  rej()
@@ -95,10 +129,34 @@ this.salt = salt;
95
129
  await sodium.ready;
96
130
  let extractedFiles = {};
97
131
  this.data.forEach(async dat => {
98
- const decryptedData = sodium.crypto_secretbox_open_easy(dat.data, dat.nonce, this.userKey);
99
- const decompressedData = zlib.inflateSync(decryptedData);
100
- const dt = decompressedData.toString('utf8');
101
- extractedFiles[dat.dataName] = await this.CSTI(dt, dat.dataType);
132
+ try{
133
+ const calculatedHash = crypto.createHash('sha256').update(dat.data).digest('hex');
134
+ if (calculatedHash !== dat.hash) {
135
+ throw new Error(`Hash mismatch for ${dat.dataName}`);
136
+ }
137
+
138
+ // Verify signature
139
+ const isValid = sodium.crypto_sign_verify_detached(
140
+ Buffer.from(dat.signature),
141
+ Buffer.from(dat.data),
142
+ this.signingKeyPair.publicKey
143
+ );
144
+ if (!isValid) {
145
+ throw new Error(`Signature verification failed for ${dat.dataName}`);
146
+ }
147
+
148
+ // Decrypt data
149
+ const decryptedData = sodium.crypto_secretbox_open_easy(
150
+ Buffer.from(dat.data),
151
+ Buffer.from(dat.nonce),
152
+ this.userKey
153
+ );
154
+ const decompressedData = zlib.inflateSync(decryptedData);
155
+ const dt = decompressedData.toString('utf8');
156
+ extractedFiles[dat.dataName] = await this.CSTI(dt, dat.dataType);
157
+ }catch{
158
+ rej()
159
+ }
102
160
  });
103
161
  res(extractedFiles);
104
162
  }catch{
@@ -141,14 +199,16 @@ this.salt = salt;
141
199
  await sodium.ready;
142
200
  const spdDataBuffer = Buffer.from(spdData, 'base64');
143
201
  const spdData2 = zlib.inflateSync(spdDataBuffer).toString('utf8');
144
- const { data, salt } = JSON.parse(spdData2);
202
+ const { data, salt, signingPublicKey } = JSON.parse(spdData2);
145
203
  const { pqcKey } = await new SPD().convertPasscodeToPQCKeySalted(passcode, new Uint8Array(salt));
204
+
146
205
  const pbk = pqcKey.publicKey;
147
206
  const spd = new SPD();
148
207
  spd.userKey = pbk;
149
208
  spd.keyPair = {
150
209
  publicKey: pbk.publicKey
151
210
  };
211
+ spd.signingKeyPair = { publicKey: Uint8Array.from(signingPublicKey) };
152
212
  spd.data = data.map(dat => ({
153
213
  dataName: dat.dataName,
154
214
  nonce: Buffer.from(dat.nonce),
@@ -157,10 +217,21 @@ this.salt = salt;
157
217
  dataType: dat.dataType
158
218
  }));
159
219
  spd.data.forEach(dat => {
160
- const calculatedHash = crypto.createHash('sha256').update(Buffer.from(dat.data)).digest('hex');
161
- if (calculatedHash !== dat.hash) {
162
- rej(new Error(`Data integrity check failed for ${dat.dataName}`));
163
- }
220
+ const encryptedBuffer = Buffer.from(dat.data);
221
+ const signatureBuffer = Buffer.from(dat.signature);
222
+ const signingPublicKeyBuffer = Uint8Array.from(signingPublicKey);
223
+
224
+ const isValid = sodium.crypto_sign_verify_detached(signatureBuffer, encryptedBuffer, signingPublicKeyBuffer);
225
+ if (!isValid) {
226
+ rej(new Error(`Signature verification failed for ${dat.dataName}`));
227
+ return;
228
+ }
229
+
230
+ const calculatedHash = crypto.createHash('sha256').update(encryptedBuffer).digest('hex');
231
+ if (calculatedHash !== dat.hash) {
232
+ rej(new Error(`Data integrity check failed for ${dat.dataName}`));
233
+ return;
234
+ }
164
235
  });
165
236
  res(spd);
166
237
  }catch{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spd-lib",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "SPD or Secure Packaged Data is a compress PQC protected file format to store sensitive data localy",
5
5
  "main": "index.js",
6
6
  "scripts": {