spd-lib 1.3.1 → 1.3.3
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 +396 -0
- package/index.d.mts +50 -0
- package/index.d.ts +50 -0
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# spd-lib-ts
|
|
2
|
+
|
|
3
|
+
**SPD (Secure Packaged Data)** — a post-quantum-hardened encrypted data format for Node.js.
|
|
4
|
+
|
|
5
|
+
Encrypt any JavaScript value (strings, numbers, objects, typed arrays, Maps, Sets, Dates, etc.) into a compressed, authenticated, tamper-proof container. Supports file storage, base64 string transport, chunked internet transfer, and streaming I/O for files larger than 2 GB.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Security model
|
|
10
|
+
|
|
11
|
+
| Layer | Algorithm | Post-quantum strength |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Key derivation | Argon2id (128 MiB, 6 iterations) | Raises brute-force cost against quantum search |
|
|
14
|
+
| Encryption | XChaCha20-Poly1305 (256-bit key) | 128-bit PQ security via Grover's algorithm |
|
|
15
|
+
| Authentication | HMAC-SHA3-512 (256-bit key, domain-separated) | 128-bit PQ security |
|
|
16
|
+
| Salt wrapping | Argon2id-derived key + XChaCha20-Poly1305 | Same as above |
|
|
17
|
+
|
|
18
|
+
Keys are domain-separated: the 512-bit Argon2id master secret is split — the first 32 bytes go to encryption, the last 32 bytes go to authentication. They are never used interchangeably.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install spd-lib-ts
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> **Requires Node.js ≥ 18** and a C++ build toolchain for the `argon2` native module.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { SPD } from 'spd-lib-ts';
|
|
36
|
+
|
|
37
|
+
const spd = new SPD();
|
|
38
|
+
await spd.setPassKey('MyStr0ng!Passphrase#2024');
|
|
39
|
+
|
|
40
|
+
await spd.addData('username', 'alice');
|
|
41
|
+
await spd.addData('apiKey', 'sk-abc123');
|
|
42
|
+
await spd.addData('config', { theme: 'dark', retries: 3 });
|
|
43
|
+
|
|
44
|
+
// Save to file
|
|
45
|
+
await spd.saveToFile('./vault.spd', 'MyStr0ng!Passphrase#2024');
|
|
46
|
+
|
|
47
|
+
// Load from file
|
|
48
|
+
const loaded = await SPD.loadFromFile('./vault.spd', 'MyStr0ng!Passphrase#2024');
|
|
49
|
+
const data = await loaded.extractData();
|
|
50
|
+
console.log(data.username); // 'alice'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## API reference
|
|
56
|
+
|
|
57
|
+
### `new SPD()`
|
|
58
|
+
|
|
59
|
+
Creates a new empty SPD instance. You must call `setPassKey()` before adding or saving data.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
### `spd.setPassKey(passcode: string): Promise<void>`
|
|
64
|
+
|
|
65
|
+
Derives encryption and authentication keys from the passcode using Argon2id. Must be called before any data operation.
|
|
66
|
+
|
|
67
|
+
**Passcode requirements:** minimum 12 characters, must contain at least 3 of: lowercase, uppercase, digits, special characters.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
await spd.setPassKey('MyStr0ng!Passphrase#2024');
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### `spd.addData(name: string, value: unknown): Promise<void>`
|
|
76
|
+
|
|
77
|
+
Encrypts and stores a single value. The name is sanitized (lowercased, non-alphanumeric chars replaced with `_`).
|
|
78
|
+
|
|
79
|
+
**Supported types:** `string`, `number`, `boolean`, `null`, `object`, `Array`, `Uint8Array`, `Uint16Array`, `Uint32Array`, `BigInt64Array`, `BigUint64Array`, `Float32Array`, `Float64Array`, `Map`, `Set`, `Date`, `RegExp`, `Error`
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
await spd.addData('score', 42);
|
|
83
|
+
await spd.addData('tags', ['a', 'b', 'c']);
|
|
84
|
+
await spd.addData('created', new Date());
|
|
85
|
+
await spd.addData('raw', new Uint8Array([1, 2, 3]));
|
|
86
|
+
await spd.addData('lookup', new Map([['key', 'val']]));
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### `spd.addMany(items: { name: string; data: unknown }[]): Promise<void>`
|
|
92
|
+
|
|
93
|
+
Batch version of `addData`. All items are encrypted in parallel.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
await spd.addMany([
|
|
97
|
+
{ name: 'firstName', data: 'Alice' },
|
|
98
|
+
{ name: 'age', data: 30 },
|
|
99
|
+
{ name: 'prefs', data: { darkMode: true } },
|
|
100
|
+
]);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### `spd.extractData(): Promise<Record<string, unknown>>`
|
|
106
|
+
|
|
107
|
+
Decrypts and returns all stored entries as a plain object.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const data = await spd.extractData();
|
|
111
|
+
console.log(data.firstName); // 'Alice'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### `spd.saveToFile(path: string, passcode: string): Promise<void>`
|
|
117
|
+
|
|
118
|
+
Saves the encrypted payload to a file (mode `0600`). Suitable for files up to ~1–2 GB.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
await spd.saveToFile('./secrets.spd', 'MyStr0ng!Passphrase#2024');
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### `SPD.loadFromFile(path: string, passcode: string): Promise<SPD>`
|
|
127
|
+
|
|
128
|
+
Loads and verifies an SPD file. The hash algorithm and Argon2 parameters are read from the file itself — no need to pass them.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const spd = await SPD.loadFromFile('./secrets.spd', 'MyStr0ng!Passphrase#2024');
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### `spd.saveToFileStreaming(path: string, passcode: string): Promise<void>`
|
|
137
|
+
### `SPD.loadFromFileStreaming(path: string, passcode: string): Promise<SPD>`
|
|
138
|
+
|
|
139
|
+
Streaming variants for files larger than 2 GB. Uses a binary wire format with 64 MB write chunks — heap usage stays bounded regardless of file size.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
await spd.saveToFileStreaming('./large.spd', 'MyStr0ng!Passphrase#2024');
|
|
143
|
+
const spd = await SPD.loadFromFileStreaming('./large.spd', 'MyStr0ng!Passphrase#2024');
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### `spd.extractDataStreaming(tmpDir: string, callback): Promise<void>`
|
|
149
|
+
|
|
150
|
+
Memory-efficient extraction using `.ssf` skeleton files. For each encrypted entry:
|
|
151
|
+
|
|
152
|
+
1. Decrypts + decompresses the entry into `<tmpDir>/<name>.ssf` (mode `0600`)
|
|
153
|
+
2. Reads the `.ssf` file and calls your callback with the name and value
|
|
154
|
+
3. Deletes the `.ssf` file before moving to the next entry
|
|
155
|
+
|
|
156
|
+
At most one entry's bytes exist on disk or in RAM at a time.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import * as fs from 'fs';
|
|
160
|
+
|
|
161
|
+
fs.mkdirSync('/tmp/spd_scratch', { recursive: true });
|
|
162
|
+
|
|
163
|
+
await spd.extractDataStreaming('/tmp/spd_scratch', async (name, value) => {
|
|
164
|
+
console.log(name, value);
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> `tmpDir` must exist before calling. Mode `0700` is recommended.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### `SPD.extractFromFileStreaming(filePath, passcode, tmpDir, callback): Promise<void>`
|
|
173
|
+
|
|
174
|
+
True constant-memory extraction from a binary `.spd` file (written by `saveToFileStreaming`). Processes one entry at a time using `.ssf` skeleton files — the full plaintext never lives in RAM.
|
|
175
|
+
|
|
176
|
+
Steps per entry:
|
|
177
|
+
1. Writes encrypted blob to `<name>_enc.ssf`
|
|
178
|
+
2. Decrypts into `<name>_dec.ssf`
|
|
179
|
+
3. Reads the `.ssf` file and calls your callback
|
|
180
|
+
4. Deletes both `.ssf` files before the next entry
|
|
181
|
+
|
|
182
|
+
Peak RAM usage is proportional to the largest single entry, not the file size.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
await SPD.extractFromFileStreaming(
|
|
186
|
+
'./huge.spd',
|
|
187
|
+
'MyStr0ng!Passphrase#2024',
|
|
188
|
+
'/tmp/spd_scratch',
|
|
189
|
+
async (name, value) => {
|
|
190
|
+
console.log(name, typeof value);
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### `spd.saveData(passcode: string): Promise<string>`
|
|
198
|
+
|
|
199
|
+
Serializes the payload to a base64 string for in-memory or network transport.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
const b64 = await spd.saveData('MyStr0ng!Passphrase#2024');
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### `SPD.loadFromString(data: string, passcode: string): Promise<SPD>`
|
|
208
|
+
|
|
209
|
+
Loads from a base64 string produced by `saveData`.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
const spd = await SPD.loadFromString(b64, 'MyStr0ng!Passphrase#2024');
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### Chunked internet transfer
|
|
218
|
+
|
|
219
|
+
Split a payload into chunks for HTTP uploads or any transport with a body-size limit.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
// Sender
|
|
223
|
+
const chunks = await spd.saveDataChunked('MyStr0ng!Passphrase#2024', 512 * 1024); // 512 KB chunks
|
|
224
|
+
// chunks is string[] — send each element, then the manifest (last element) last
|
|
225
|
+
|
|
226
|
+
// Receiver — pass all chunks in order including the manifest
|
|
227
|
+
const spd = await SPD.loadFromChunks(chunks, 'MyStr0ng!Passphrase#2024');
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The last element of the array is always a JSON manifest (`{ totalChunks, chunkSize, totalBytes, version }`). The receiver validates chunk count and byte count before decrypting.
|
|
231
|
+
|
|
232
|
+
Default chunk size is **1 MB**. Tune down for strict API body limits.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### `spd.changePasscode(oldPasscode: string, newPasscode: string): Promise<void>`
|
|
237
|
+
|
|
238
|
+
Re-derives keys under the new passcode and re-encrypts all data. Verifies the old passcode via MAC before doing anything.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
await spd.changePasscode('MyStr0ng!Passphrase#2024', 'EvenStr0nger!Pass#9999');
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
### `spd.setHash(hash: 'sha3-512' | 'sha256' | 'sha512'): void`
|
|
247
|
+
|
|
248
|
+
Sets the hash algorithm used for per-entry integrity checks. Default is `'sha3-512'`. Must be set before adding data. The chosen algorithm is embedded in the payload and restored automatically on load.
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
spd.setHash('sha3-512'); // default, recommended
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### `spd.setCompressionLevel(level: number): void`
|
|
257
|
+
|
|
258
|
+
Sets the zlib deflate compression level (1–9). Default is `9` (maximum compression).
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
spd.setCompressionLevel(6); // faster, slightly larger
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### `spd.destroy()` / `spd.clearCache()`
|
|
267
|
+
|
|
268
|
+
Securely zeros all key material in place using `sodium.memzero()`, then clears all stored data. Call this when you are done with a session.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
spd.destroy();
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
### `SPD.deriveKeys(passcode, salt)` / `SPD.derivePBK(passcode, salt)`
|
|
277
|
+
|
|
278
|
+
Low-level static helpers exposed for advanced use cases (e.g. deriving keys outside of an SPD container).
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## SPDVault — in-memory key vault
|
|
283
|
+
|
|
284
|
+
`SPDVault` is a time-limited in-memory store for passcodes or keys. Keys expire automatically after a configurable timeout and are cleared on access renewal.
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import { SPDVault } from 'spd-lib-ts';
|
|
288
|
+
|
|
289
|
+
const vault = new SPDVault(300_000); // 5-minute TTL
|
|
290
|
+
|
|
291
|
+
// Generate a random high-entropy key and store it
|
|
292
|
+
vault.genKey('session');
|
|
293
|
+
|
|
294
|
+
// Store your own key
|
|
295
|
+
vault.pushKey('myKey', 'MyStr0ng!Passphrase#2024');
|
|
296
|
+
|
|
297
|
+
// Retrieve (resets TTL)
|
|
298
|
+
const key = vault.pullKey('myKey'); // 'MyStr0ng!Passphrase#2024'
|
|
299
|
+
|
|
300
|
+
// Update (requires old value to match)
|
|
301
|
+
vault.updateKey('myKey', 'MyStr0ng!Passphrase#2024', 'NewPass!99');
|
|
302
|
+
|
|
303
|
+
// Delete one key
|
|
304
|
+
vault.destroyKey('myKey');
|
|
305
|
+
|
|
306
|
+
// Stop all timers (keys stay in memory)
|
|
307
|
+
vault.stop();
|
|
308
|
+
|
|
309
|
+
// Wipe everything
|
|
310
|
+
vault.destroy();
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Generated keys are 500–699 characters of cryptographically random characters drawn from a 91-character charset using rejection sampling (no modulo bias).
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## SPDLegacy
|
|
318
|
+
|
|
319
|
+
`SPDLegacy` is a backwards-compatible class for reading files produced by SPD v1.x. It uses `crypto_secretbox_easy` (XSalsa20-Poly1305) and PBKDF2 key derivation. **Do not use for new data** — migrate to `SPD` instead.
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import { SPDLegacy } from 'spd-lib-ts';
|
|
323
|
+
|
|
324
|
+
const spd = await SPDLegacy.loadFromFile('./old.spd', 'passcode');
|
|
325
|
+
const data = await spd.extractData();
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Full example — save and load a config
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import { SPD } from 'spd-lib-ts';
|
|
334
|
+
import * as path from 'path';
|
|
335
|
+
|
|
336
|
+
const FILE = path.join(process.env.HOME!, '.myapp', 'config.spd');
|
|
337
|
+
const PASS = process.env.APP_PASS!;
|
|
338
|
+
|
|
339
|
+
async function saveConfig(config: Record<string, unknown>) {
|
|
340
|
+
const spd = new SPD();
|
|
341
|
+
await spd.setPassKey(PASS);
|
|
342
|
+
await spd.addMany(Object.entries(config).map(([name, data]) => ({ name, data })));
|
|
343
|
+
await spd.saveToFile(FILE, PASS);
|
|
344
|
+
spd.destroy();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function loadConfig(): Promise<Record<string, unknown>> {
|
|
348
|
+
const spd = await SPD.loadFromFile(FILE, PASS);
|
|
349
|
+
const data = await spd.extractData();
|
|
350
|
+
spd.destroy();
|
|
351
|
+
return data;
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Full example — chunked HTTP upload
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
// server sends:
|
|
361
|
+
const spd = new SPD();
|
|
362
|
+
await spd.setPassKey(PASS);
|
|
363
|
+
await spd.addData('payload', largeObject);
|
|
364
|
+
const chunks = await spd.saveDataChunked(PASS, 256 * 1024); // 256 KB per chunk
|
|
365
|
+
spd.destroy();
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
368
|
+
await fetch('https://example.com/upload', {
|
|
369
|
+
method: 'POST',
|
|
370
|
+
body: JSON.stringify({ index: i, chunk: chunks[i] }),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// receiver collects all chunks in order and loads:
|
|
375
|
+
const spd = await SPD.loadFromChunks(receivedChunks, PASS);
|
|
376
|
+
const data = await spd.extractData();
|
|
377
|
+
spd.destroy();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Versioning
|
|
383
|
+
|
|
384
|
+
| npm version | SPD format version | Notes |
|
|
385
|
+
|---|---|---|
|
|
386
|
+
| 1.3.1 | v26 | Hash algo + Argon2 params embedded in payload, secure key zeroing, `null` type support |
|
|
387
|
+
| 1.3.0 | v25 | 512-bit Argon2id master secret, domain-separated AEAD + HMAC keys, HMAC-SHA3-512 |
|
|
388
|
+
| < 1.3.0 | v24 | 256-bit Argon2id, single key for AEAD + MAC |
|
|
389
|
+
|
|
390
|
+
SPD format versions are not cross-compatible. Use `SPDLegacy` to read v24 and earlier files.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## License
|
|
395
|
+
|
|
396
|
+
ISC
|
package/index.d.mts
CHANGED
|
@@ -98,6 +98,29 @@ declare class SPD {
|
|
|
98
98
|
addData(dataName: string, value: unknown): Promise<void>;
|
|
99
99
|
addMany(items: DataInput[]): Promise<void>;
|
|
100
100
|
extractData(): Promise<Record<string, unknown>>;
|
|
101
|
+
/**
|
|
102
|
+
* Memory-efficient streaming extraction.
|
|
103
|
+
*
|
|
104
|
+
* For each encrypted entry, this method:
|
|
105
|
+
* 1. Decrypts and decompresses the entry into a temp file in `tmpDir`
|
|
106
|
+
* (named `<entryName>.ssf`, mode 0600)
|
|
107
|
+
* 2. Reads the .ssf file and calls your `callback` with the name + value
|
|
108
|
+
* 3. Immediately deletes the .ssf file before moving to the next entry
|
|
109
|
+
*
|
|
110
|
+
* At most one entry's decrypted bytes exist on disk or in RAM at a time,
|
|
111
|
+
* so this works for arbitrarily large SPD files as long as a single entry
|
|
112
|
+
* fits in memory (which is always the case — entries are discrete values).
|
|
113
|
+
*
|
|
114
|
+
* @param tmpDir Directory to write temp files into (must exist, mode 0700 recommended)
|
|
115
|
+
* @param callback Called once per entry in order. Await is respected — the next
|
|
116
|
+
* entry won't be processed until your callback resolves.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* await spd.extractDataStreaming('/tmp/spd_scratch', async (name, value) => {
|
|
120
|
+
* console.log(name, value);
|
|
121
|
+
* });
|
|
122
|
+
*/
|
|
123
|
+
extractDataStreaming(tmpDir: string, callback: (name: string, value: unknown) => Promise<void> | void): Promise<void>;
|
|
101
124
|
destroy(): void;
|
|
102
125
|
clearCache(): void;
|
|
103
126
|
saveToFile(outputPath: string, passcode: string): Promise<void>;
|
|
@@ -115,6 +138,33 @@ declare class SPD {
|
|
|
115
138
|
saveDataChunked(passcode: string, chunkSize?: number): Promise<string[]>;
|
|
116
139
|
static loadFromFile(filePath: string, passcode: string): Promise<SPD>;
|
|
117
140
|
static loadFromFileStreaming(filePath: string, passcode: string): Promise<SPD>;
|
|
141
|
+
/**
|
|
142
|
+
* True constant-memory streaming extract from a binary SPD file.
|
|
143
|
+
*
|
|
144
|
+
* Unlike `loadFromFileStreaming` (which buffers the entire plaintext into RAM),
|
|
145
|
+
* this method pipes the inflate stream through a byte-by-byte parser that:
|
|
146
|
+
* 1. Reads and verifies the file header + MAC
|
|
147
|
+
* 2. Parses the metadata block to derive keys
|
|
148
|
+
* 3. Processes one entry at a time:
|
|
149
|
+
* a. Writes the encrypted entry to `<name>_enc.ssf` in `tmpDir` (mode 0600)
|
|
150
|
+
* b. Decrypts it, writes the plaintext to `<name>_dec.ssf`
|
|
151
|
+
* c. Reads the `.ssf` file and calls your `callback`
|
|
152
|
+
* d. Deletes both `.ssf` files before touching the next entry
|
|
153
|
+
*
|
|
154
|
+
* Peak RAM usage is proportional to the largest single entry, not the file size.
|
|
155
|
+
* Files of any size are supported as long as individual entries fit in memory.
|
|
156
|
+
*
|
|
157
|
+
* @param filePath Path to the `.spd` file written by `saveToFileStreaming`
|
|
158
|
+
* @param passcode Passcode used when the file was saved
|
|
159
|
+
* @param tmpDir Scratch directory for temp files (must exist, mode 0700 recommended)
|
|
160
|
+
* @param callback Called once per entry in order. Next entry waits for this to resolve.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* await SPD.extractFromFileStreaming('./huge.spd', 'MyPass!', '/tmp/spd', async (name, value) => {
|
|
164
|
+
* console.log(name, typeof value);
|
|
165
|
+
* });
|
|
166
|
+
*/
|
|
167
|
+
static extractFromFileStreaming(filePath: string, passcode: string, tmpDir: string, callback: (name: string, value: unknown) => Promise<void> | void): Promise<void>;
|
|
118
168
|
static loadFromString(data: string, passcode: string): Promise<SPD>;
|
|
119
169
|
static loadFromChunks(chunks: string[], passcode: string): Promise<SPD>;
|
|
120
170
|
static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number): Promise<PBKResult>;
|
package/index.d.ts
CHANGED
|
@@ -98,6 +98,29 @@ declare class SPD {
|
|
|
98
98
|
addData(dataName: string, value: unknown): Promise<void>;
|
|
99
99
|
addMany(items: DataInput[]): Promise<void>;
|
|
100
100
|
extractData(): Promise<Record<string, unknown>>;
|
|
101
|
+
/**
|
|
102
|
+
* Memory-efficient streaming extraction.
|
|
103
|
+
*
|
|
104
|
+
* For each encrypted entry, this method:
|
|
105
|
+
* 1. Decrypts and decompresses the entry into a temp file in `tmpDir`
|
|
106
|
+
* (named `<entryName>.ssf`, mode 0600)
|
|
107
|
+
* 2. Reads the .ssf file and calls your `callback` with the name + value
|
|
108
|
+
* 3. Immediately deletes the .ssf file before moving to the next entry
|
|
109
|
+
*
|
|
110
|
+
* At most one entry's decrypted bytes exist on disk or in RAM at a time,
|
|
111
|
+
* so this works for arbitrarily large SPD files as long as a single entry
|
|
112
|
+
* fits in memory (which is always the case — entries are discrete values).
|
|
113
|
+
*
|
|
114
|
+
* @param tmpDir Directory to write temp files into (must exist, mode 0700 recommended)
|
|
115
|
+
* @param callback Called once per entry in order. Await is respected — the next
|
|
116
|
+
* entry won't be processed until your callback resolves.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* await spd.extractDataStreaming('/tmp/spd_scratch', async (name, value) => {
|
|
120
|
+
* console.log(name, value);
|
|
121
|
+
* });
|
|
122
|
+
*/
|
|
123
|
+
extractDataStreaming(tmpDir: string, callback: (name: string, value: unknown) => Promise<void> | void): Promise<void>;
|
|
101
124
|
destroy(): void;
|
|
102
125
|
clearCache(): void;
|
|
103
126
|
saveToFile(outputPath: string, passcode: string): Promise<void>;
|
|
@@ -115,6 +138,33 @@ declare class SPD {
|
|
|
115
138
|
saveDataChunked(passcode: string, chunkSize?: number): Promise<string[]>;
|
|
116
139
|
static loadFromFile(filePath: string, passcode: string): Promise<SPD>;
|
|
117
140
|
static loadFromFileStreaming(filePath: string, passcode: string): Promise<SPD>;
|
|
141
|
+
/**
|
|
142
|
+
* True constant-memory streaming extract from a binary SPD file.
|
|
143
|
+
*
|
|
144
|
+
* Unlike `loadFromFileStreaming` (which buffers the entire plaintext into RAM),
|
|
145
|
+
* this method pipes the inflate stream through a byte-by-byte parser that:
|
|
146
|
+
* 1. Reads and verifies the file header + MAC
|
|
147
|
+
* 2. Parses the metadata block to derive keys
|
|
148
|
+
* 3. Processes one entry at a time:
|
|
149
|
+
* a. Writes the encrypted entry to `<name>_enc.ssf` in `tmpDir` (mode 0600)
|
|
150
|
+
* b. Decrypts it, writes the plaintext to `<name>_dec.ssf`
|
|
151
|
+
* c. Reads the `.ssf` file and calls your `callback`
|
|
152
|
+
* d. Deletes both `.ssf` files before touching the next entry
|
|
153
|
+
*
|
|
154
|
+
* Peak RAM usage is proportional to the largest single entry, not the file size.
|
|
155
|
+
* Files of any size are supported as long as individual entries fit in memory.
|
|
156
|
+
*
|
|
157
|
+
* @param filePath Path to the `.spd` file written by `saveToFileStreaming`
|
|
158
|
+
* @param passcode Passcode used when the file was saved
|
|
159
|
+
* @param tmpDir Scratch directory for temp files (must exist, mode 0700 recommended)
|
|
160
|
+
* @param callback Called once per entry in order. Next entry waits for this to resolve.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* await SPD.extractFromFileStreaming('./huge.spd', 'MyPass!', '/tmp/spd', async (name, value) => {
|
|
164
|
+
* console.log(name, typeof value);
|
|
165
|
+
* });
|
|
166
|
+
*/
|
|
167
|
+
static extractFromFileStreaming(filePath: string, passcode: string, tmpDir: string, callback: (name: string, value: unknown) => Promise<void> | void): Promise<void>;
|
|
118
168
|
static loadFromString(data: string, passcode: string): Promise<SPD>;
|
|
119
169
|
static loadFromChunks(chunks: string[], passcode: string): Promise<SPD>;
|
|
120
170
|
static derivePBK(passcode: string, salt: Uint8Array, memory?: number, time?: number): Promise<PBKResult>;
|