spd-lib 1.1.6 → 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 +80 -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()
@@ -96,10 +130,30 @@ this.salt = salt;
96
130
  let extractedFiles = {};
97
131
  this.data.forEach(async dat => {
98
132
  try{
99
- const decryptedData = sodium.crypto_secretbox_open_easy(dat.data, dat.nonce, this.userKey);
100
- const decompressedData = zlib.inflateSync(decryptedData);
101
- const dt = decompressedData.toString('utf8');
102
- extractedFiles[dat.dataName] = await this.CSTI(dt, dat.dataType);
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);
103
157
  }catch{
104
158
  rej()
105
159
  }
@@ -145,14 +199,16 @@ this.salt = salt;
145
199
  await sodium.ready;
146
200
  const spdDataBuffer = Buffer.from(spdData, 'base64');
147
201
  const spdData2 = zlib.inflateSync(spdDataBuffer).toString('utf8');
148
- const { data, salt } = JSON.parse(spdData2);
202
+ const { data, salt, signingPublicKey } = JSON.parse(spdData2);
149
203
  const { pqcKey } = await new SPD().convertPasscodeToPQCKeySalted(passcode, new Uint8Array(salt));
204
+
150
205
  const pbk = pqcKey.publicKey;
151
206
  const spd = new SPD();
152
207
  spd.userKey = pbk;
153
208
  spd.keyPair = {
154
209
  publicKey: pbk.publicKey
155
210
  };
211
+ spd.signingKeyPair = { publicKey: Uint8Array.from(signingPublicKey) };
156
212
  spd.data = data.map(dat => ({
157
213
  dataName: dat.dataName,
158
214
  nonce: Buffer.from(dat.nonce),
@@ -161,10 +217,21 @@ this.salt = salt;
161
217
  dataType: dat.dataType
162
218
  }));
163
219
  spd.data.forEach(dat => {
164
- const calculatedHash = crypto.createHash('sha256').update(Buffer.from(dat.data)).digest('hex');
165
- if (calculatedHash !== dat.hash) {
166
- rej(new Error(`Data integrity check failed for ${dat.dataName}`));
167
- }
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
+ }
168
235
  });
169
236
  res(spd);
170
237
  }catch{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spd-lib",
3
- "version": "1.1.6",
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": {