@techbulls/encrypted-s3-store 1.0.5 → 1.1.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/README.md +84 -18
- package/dist/client.d.ts +23 -20
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +181 -60
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +57 -7
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -1
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +8 -2
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +19 -11
- package/dist/utils.js.map +1 -0
- package/package.json +49 -16
package/README.md
CHANGED
|
@@ -308,16 +308,17 @@ interface CustomEncryption {
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
interface EncryptResult {
|
|
311
|
-
data: Buffer;
|
|
312
|
-
metadata: EncryptionMetadata;
|
|
311
|
+
data: Buffer; // The encrypted data
|
|
312
|
+
metadata: EncryptionMetadata; // Metadata to store for decryption
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
interface EncryptionMetadata {
|
|
316
|
-
[key: string]: string;
|
|
316
|
+
[key: string]: string; // Key-value pairs stored in S3 object metadata
|
|
317
317
|
}
|
|
318
318
|
```
|
|
319
319
|
|
|
320
320
|
**Notes:**
|
|
321
|
+
|
|
321
322
|
- Both `encrypt` and `decrypt` functions must be provided together
|
|
322
323
|
- Metadata keys are automatically prefixed with `x-amz-custom-` when stored in S3
|
|
323
324
|
- Custom encryption uses `x-amz-encryption: 'custom'` as the marker in S3 metadata
|
|
@@ -356,30 +357,17 @@ Uses scrypt with the following parameters:
|
|
|
356
357
|
Encryption metadata is stored in S3 object metadata:
|
|
357
358
|
|
|
358
359
|
**Default AES-256-GCM:**
|
|
360
|
+
|
|
359
361
|
- `x-amz-iv` - Base64-encoded initialization vector
|
|
360
362
|
- `x-amz-salt` - Base64-encoded salt for key derivation
|
|
361
363
|
- `x-amz-auth-tag` - Base64-encoded authentication tag
|
|
362
364
|
- `x-amz-encryption` - Set to `aes-256-gcm`
|
|
363
365
|
|
|
364
366
|
**Custom Encryption:**
|
|
367
|
+
|
|
365
368
|
- `x-amz-custom-*` - Custom metadata keys (prefixed automatically)
|
|
366
369
|
- `x-amz-encryption` - Set to `custom`
|
|
367
370
|
|
|
368
|
-
## Error Handling
|
|
369
|
-
|
|
370
|
-
The library throws descriptive errors for common issues:
|
|
371
|
-
|
|
372
|
-
```typescript
|
|
373
|
-
try {
|
|
374
|
-
await store.upload({ Bucket: 'my-bucket', Key: 'test.txt', Body: 'content' });
|
|
375
|
-
} catch (error) {
|
|
376
|
-
// Handle errors:
|
|
377
|
-
// - "Encryption key not provided."
|
|
378
|
-
// - "Body is required for upload"
|
|
379
|
-
// - "No body returned from S3"
|
|
380
|
-
}
|
|
381
|
-
```
|
|
382
|
-
|
|
383
371
|
## TypeScript
|
|
384
372
|
|
|
385
373
|
The library is written in TypeScript and exports all types:
|
|
@@ -426,6 +414,84 @@ await store.client.send(
|
|
|
426
414
|
);
|
|
427
415
|
```
|
|
428
416
|
|
|
417
|
+
## Advanced Configuration
|
|
418
|
+
|
|
419
|
+
### Custom Scrypt Parameters
|
|
420
|
+
|
|
421
|
+
For higher security requirements, you can configure the scrypt key derivation parameters:
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
const store = new S3ObjectStore(
|
|
425
|
+
s3Client,
|
|
426
|
+
'your-key',
|
|
427
|
+
'encrypt',
|
|
428
|
+
undefined, // no custom encryption
|
|
429
|
+
{
|
|
430
|
+
N: 65536, // 2^16, 4x more secure than default (16384)
|
|
431
|
+
r: 8, // block size (default)
|
|
432
|
+
p: 1, // parallelization (default)
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Higher `N` values increase security but also increase computation time and memory usage.
|
|
438
|
+
|
|
439
|
+
## Error Handling
|
|
440
|
+
|
|
441
|
+
The library throws descriptive errors. Here's how to handle common cases:
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
try {
|
|
445
|
+
await store.upload({
|
|
446
|
+
Bucket: 'my-bucket',
|
|
447
|
+
Key: 'file.txt',
|
|
448
|
+
Body: 'content',
|
|
449
|
+
});
|
|
450
|
+
} catch (error) {
|
|
451
|
+
if (error instanceof Error) {
|
|
452
|
+
if (error.message.includes('Bucket is required')) {
|
|
453
|
+
console.error('Missing bucket name');
|
|
454
|
+
} else if (error.message.includes('encryption metadata is missing')) {
|
|
455
|
+
console.error('Trying to decrypt an unencrypted file - use passthrough mode');
|
|
456
|
+
} else if (error.message.includes('Failed to upload')) {
|
|
457
|
+
console.error('S3 upload failed - check credentials and permissions');
|
|
458
|
+
} else {
|
|
459
|
+
console.error('Error:', error.message);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Security Best Practices
|
|
466
|
+
|
|
467
|
+
1. **Key Management**
|
|
468
|
+
- Never hardcode encryption keys in source code
|
|
469
|
+
- Use environment variables or secret management services (AWS Secrets Manager, HashiCorp Vault)
|
|
470
|
+
- Rotate keys periodically
|
|
471
|
+
|
|
472
|
+
2. **Key Storage**
|
|
473
|
+
- Store keys in encrypted form when at rest
|
|
474
|
+
- Never commit keys to version control
|
|
475
|
+
|
|
476
|
+
3. **Defense in Depth**
|
|
477
|
+
- Consider using this library WITH S3 server-side encryption for additional protection
|
|
478
|
+
- Use HTTPS for all S3 operations (default with AWS SDK)
|
|
479
|
+
- Enable AWS CloudTrail for audit logs
|
|
480
|
+
|
|
481
|
+
4. **Access Control**
|
|
482
|
+
- Use least-privilege IAM policies
|
|
483
|
+
- Enable S3 bucket versioning for recovery
|
|
484
|
+
|
|
485
|
+
## Platform Support
|
|
486
|
+
|
|
487
|
+
This library is **Node.js only** and does not support browsers. It uses Node.js built-in modules:
|
|
488
|
+
|
|
489
|
+
- `node:crypto` (for AES-256-GCM encryption and scrypt)
|
|
490
|
+
- `node:stream` (for streaming)
|
|
491
|
+
- `node:util` (for promisify)
|
|
492
|
+
|
|
493
|
+
For browser environments, consider using server-side encryption or alternative libraries that use the Web Crypto API.
|
|
494
|
+
|
|
429
495
|
## Requirements
|
|
430
496
|
|
|
431
497
|
- Node.js 18+
|
package/dist/client.d.ts
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* Provides transparent encryption/decryption for S3 objects using AES-256-GCM.
|
|
4
4
|
*
|
|
5
5
|
* @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
|
|
6
|
-
* @license
|
|
6
|
+
* @license MIT
|
|
7
7
|
*/
|
|
8
8
|
import { S3Client } from '@aws-sdk/client-s3';
|
|
9
|
-
import { CustomEncryption, IDownload, IUpload, ModeType } from './types.js';
|
|
10
|
-
import { Readable } from 'node:stream';
|
|
9
|
+
import { CustomEncryption, DownloadResult, IDownload, IUpload, ModeType, ScryptOptions } from './types.js';
|
|
11
10
|
/**
|
|
12
11
|
* S3 client wrapper with transparent end-to-end encryption support.
|
|
13
12
|
*
|
|
@@ -31,17 +30,17 @@ import { Readable } from 'node:stream';
|
|
|
31
30
|
*/
|
|
32
31
|
export declare class S3ObjectStore {
|
|
33
32
|
/** @type {ModeType} Current operation mode - 'encrypt' or 'passthrough' */
|
|
34
|
-
mode: ModeType;
|
|
33
|
+
readonly mode: ModeType;
|
|
35
34
|
/** @type {string} Encryption key used for AES-256-GCM encryption */
|
|
36
|
-
key
|
|
35
|
+
private readonly key;
|
|
37
36
|
/** @type {string | undefined} Default S3 bucket for all operations */
|
|
38
|
-
bucket?: string;
|
|
39
|
-
/** @type {string} Encryption algorithm identifier (always 'aes-256-gcm') */
|
|
40
|
-
algorithm: string;
|
|
37
|
+
readonly bucket?: string;
|
|
41
38
|
/** @type {S3Client} The underlying AWS S3 client instance */
|
|
42
|
-
client: S3Client;
|
|
39
|
+
readonly client: S3Client;
|
|
43
40
|
/** @type {CustomEncryption | undefined} Optional custom encryption/decryption functions */
|
|
44
|
-
customEncryption
|
|
41
|
+
private readonly customEncryption?;
|
|
42
|
+
/** @type {ScryptOptions | undefined} Optional scrypt parameters for key derivation */
|
|
43
|
+
private readonly scryptOptions?;
|
|
45
44
|
/**
|
|
46
45
|
* Creates a new S3ObjectStore instance.
|
|
47
46
|
*
|
|
@@ -49,7 +48,8 @@ export declare class S3ObjectStore {
|
|
|
49
48
|
* @param key - Encryption key for AES-256-GCM. Falls back to ENCRYPTION_KEY environment variable if not provided.
|
|
50
49
|
* @param mode - Operation mode ('encrypt' or 'passthrough'). Defaults to 'encrypt'.
|
|
51
50
|
* @param customEncryption - Optional custom encryption/decryption functions to replace the default AES-256-GCM implementation.
|
|
52
|
-
* @
|
|
51
|
+
* @param scryptOptions - Optional scrypt parameters for key derivation (N, r, p). Defaults: N=16384, r=8, p=1.
|
|
52
|
+
* @throws {Error} If no encryption key is provided in 'encrypt' mode and ENCRYPTION_KEY environment variable is not set
|
|
53
53
|
*
|
|
54
54
|
* @example
|
|
55
55
|
* ```typescript
|
|
@@ -59,17 +59,24 @@ export declare class S3ObjectStore {
|
|
|
59
59
|
* // With environment variable (ENCRYPTION_KEY)
|
|
60
60
|
* const store = new S3ObjectStore(s3Client);
|
|
61
61
|
*
|
|
62
|
-
* // With passthrough mode (no encryption)
|
|
63
|
-
* const store = new S3ObjectStore(s3Client,
|
|
62
|
+
* // With passthrough mode (no encryption key required)
|
|
63
|
+
* const store = new S3ObjectStore(s3Client, undefined, 'passthrough');
|
|
64
64
|
*
|
|
65
65
|
* // With custom encryption
|
|
66
66
|
* const store = new S3ObjectStore(s3Client, 'key', 'encrypt', {
|
|
67
67
|
* encrypt: (data, key) => ({ data: myEncrypt(data, key), metadata: { myParam: '...' } }),
|
|
68
68
|
* decrypt: (data, key, metadata) => myDecrypt(data, key, metadata.myParam)
|
|
69
69
|
* });
|
|
70
|
+
*
|
|
71
|
+
* // With custom scrypt parameters for higher security
|
|
72
|
+
* const store = new S3ObjectStore(s3Client, 'key', 'encrypt', undefined, {
|
|
73
|
+
* N: 65536, // 2^16, 4x more secure than default
|
|
74
|
+
* r: 8,
|
|
75
|
+
* p: 1,
|
|
76
|
+
* });
|
|
70
77
|
* ```
|
|
71
78
|
*/
|
|
72
|
-
constructor(client: S3Client, key?: string, mode?: ModeType, customEncryption?: CustomEncryption);
|
|
79
|
+
constructor(client: S3Client, key?: string, mode?: ModeType, customEncryption?: CustomEncryption, scryptOptions?: ScryptOptions);
|
|
73
80
|
/**
|
|
74
81
|
* Uploads data to S3 with optional encryption.
|
|
75
82
|
*
|
|
@@ -154,10 +161,6 @@ export declare class S3ObjectStore {
|
|
|
154
161
|
* const content = Buffer.concat(chunks).toString('utf8');
|
|
155
162
|
* ```
|
|
156
163
|
*/
|
|
157
|
-
download(input: IDownload): Promise<
|
|
158
|
-
Body: Readable;
|
|
159
|
-
ContentType: string | undefined;
|
|
160
|
-
ContentLength: number | undefined;
|
|
161
|
-
Metadata: Record<string, string> | undefined;
|
|
162
|
-
}>;
|
|
164
|
+
download(input: IDownload): Promise<DownloadResult>;
|
|
163
165
|
}
|
|
166
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAsC,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAElF,OAAO,EACL,gBAAgB,EAChB,cAAc,EAEd,SAAS,EACT,OAAO,EACP,QAAQ,EACR,aAAa,EACd,MAAM,YAAY,CAAC;AA8BpB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,aAAa;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAExB,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAE1B,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAmB;IAErD,sFAAsF;IACtF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAgB;IAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;gBAED,MAAM,EAAE,QAAQ,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,QAAQ,EACf,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,aAAa,CAAC,EAAE,aAAa;IAiB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;IACG,MAAM,CAAC,KAAK,EAAE,OAAO;IAiK3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACG,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC;CAwJ1D"}
|
package/dist/client.js
CHANGED
|
@@ -3,12 +3,32 @@
|
|
|
3
3
|
* Provides transparent encryption/decryption for S3 objects using AES-256-GCM.
|
|
4
4
|
*
|
|
5
5
|
* @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
|
|
6
|
-
* @license
|
|
6
|
+
* @license MIT
|
|
7
7
|
*/
|
|
8
8
|
import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
9
9
|
import { deriveKey } from './utils.js';
|
|
10
10
|
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
|
|
11
11
|
import { Readable } from 'node:stream';
|
|
12
|
+
/** S3 metadata size limit in bytes */
|
|
13
|
+
const S3_METADATA_SIZE_LIMIT = 2048;
|
|
14
|
+
/**
|
|
15
|
+
* Calculates the total size of S3 metadata in bytes.
|
|
16
|
+
* S3 counts both keys and values toward the 2KB limit.
|
|
17
|
+
*/
|
|
18
|
+
function calculateMetadataSize(metadata) {
|
|
19
|
+
return Object.entries(metadata).reduce((sum, [key, value]) => sum + key.length + value.length, 0);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validates that S3 metadata size doesn't exceed the 2KB limit.
|
|
23
|
+
* @throws {Error} If metadata exceeds the limit
|
|
24
|
+
*/
|
|
25
|
+
function validateMetadataSize(metadata) {
|
|
26
|
+
const size = calculateMetadataSize(metadata);
|
|
27
|
+
if (size > S3_METADATA_SIZE_LIMIT) {
|
|
28
|
+
throw new Error(`S3 metadata size (${size} bytes) exceeds the 2KB limit. ` +
|
|
29
|
+
'Reduce custom metadata or encryption metadata size.');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
12
32
|
/**
|
|
13
33
|
* S3 client wrapper with transparent end-to-end encryption support.
|
|
14
34
|
*
|
|
@@ -38,7 +58,8 @@ export class S3ObjectStore {
|
|
|
38
58
|
* @param key - Encryption key for AES-256-GCM. Falls back to ENCRYPTION_KEY environment variable if not provided.
|
|
39
59
|
* @param mode - Operation mode ('encrypt' or 'passthrough'). Defaults to 'encrypt'.
|
|
40
60
|
* @param customEncryption - Optional custom encryption/decryption functions to replace the default AES-256-GCM implementation.
|
|
41
|
-
* @
|
|
61
|
+
* @param scryptOptions - Optional scrypt parameters for key derivation (N, r, p). Defaults: N=16384, r=8, p=1.
|
|
62
|
+
* @throws {Error} If no encryption key is provided in 'encrypt' mode and ENCRYPTION_KEY environment variable is not set
|
|
42
63
|
*
|
|
43
64
|
* @example
|
|
44
65
|
* ```typescript
|
|
@@ -48,27 +69,34 @@ export class S3ObjectStore {
|
|
|
48
69
|
* // With environment variable (ENCRYPTION_KEY)
|
|
49
70
|
* const store = new S3ObjectStore(s3Client);
|
|
50
71
|
*
|
|
51
|
-
* // With passthrough mode (no encryption)
|
|
52
|
-
* const store = new S3ObjectStore(s3Client,
|
|
72
|
+
* // With passthrough mode (no encryption key required)
|
|
73
|
+
* const store = new S3ObjectStore(s3Client, undefined, 'passthrough');
|
|
53
74
|
*
|
|
54
75
|
* // With custom encryption
|
|
55
76
|
* const store = new S3ObjectStore(s3Client, 'key', 'encrypt', {
|
|
56
77
|
* encrypt: (data, key) => ({ data: myEncrypt(data, key), metadata: { myParam: '...' } }),
|
|
57
78
|
* decrypt: (data, key, metadata) => myDecrypt(data, key, metadata.myParam)
|
|
58
79
|
* });
|
|
80
|
+
*
|
|
81
|
+
* // With custom scrypt parameters for higher security
|
|
82
|
+
* const store = new S3ObjectStore(s3Client, 'key', 'encrypt', undefined, {
|
|
83
|
+
* N: 65536, // 2^16, 4x more secure than default
|
|
84
|
+
* r: 8,
|
|
85
|
+
* p: 1,
|
|
86
|
+
* });
|
|
59
87
|
* ```
|
|
60
88
|
*/
|
|
61
|
-
constructor(client, key, mode, customEncryption) {
|
|
62
|
-
/** @type {string} Encryption algorithm identifier (always 'aes-256-gcm') */
|
|
63
|
-
this.algorithm = 'aes-256-gcm';
|
|
89
|
+
constructor(client, key, mode, customEncryption, scryptOptions) {
|
|
64
90
|
this.client = client;
|
|
65
|
-
const envEncryptionKey = process.env.ENCRYPTION_KEY;
|
|
66
|
-
if (!key && !envEncryptionKey) {
|
|
67
|
-
throw new Error('Encryption key not provided.');
|
|
68
|
-
}
|
|
69
|
-
this.key = (key || envEncryptionKey);
|
|
70
91
|
this.mode = mode ?? 'encrypt';
|
|
71
92
|
this.customEncryption = customEncryption;
|
|
93
|
+
this.scryptOptions = scryptOptions;
|
|
94
|
+
const envEncryptionKey = process.env.ENCRYPTION_KEY;
|
|
95
|
+
// Only require encryption key in 'encrypt' mode
|
|
96
|
+
if (this.mode === 'encrypt' && !key && !envEncryptionKey) {
|
|
97
|
+
throw new Error('Encryption key is required when mode is "encrypt".');
|
|
98
|
+
}
|
|
99
|
+
this.key = (key || envEncryptionKey || '');
|
|
72
100
|
}
|
|
73
101
|
/**
|
|
74
102
|
* Uploads data to S3 with optional encryption.
|
|
@@ -120,17 +148,36 @@ export class S3ObjectStore {
|
|
|
120
148
|
* ```
|
|
121
149
|
*/
|
|
122
150
|
async upload(input) {
|
|
151
|
+
/** Validate required input parameters */
|
|
152
|
+
const bucket = input.Bucket || this.bucket;
|
|
153
|
+
if (!bucket) {
|
|
154
|
+
throw new Error('Bucket is required. Provide it in upload() or set a default bucket.');
|
|
155
|
+
}
|
|
156
|
+
if (!input.Key) {
|
|
157
|
+
throw new Error('Key is required for upload.');
|
|
158
|
+
}
|
|
159
|
+
if (input.Key.startsWith('/')) {
|
|
160
|
+
throw new Error('S3 Key should not start with "/" - use relative paths only.');
|
|
161
|
+
}
|
|
162
|
+
if (input.Key.includes('\\')) {
|
|
163
|
+
throw new Error('S3 Key should not contain backslashes - use forward slashes for paths.');
|
|
164
|
+
}
|
|
123
165
|
if (!input.Body) {
|
|
124
|
-
throw new Error('Body is required for upload');
|
|
166
|
+
throw new Error('Body is required for upload.');
|
|
125
167
|
}
|
|
126
168
|
/** Resolve mode: input.mode takes precedence, fallback to client default */
|
|
127
169
|
const mode = input?.Mode === 'encrypt' || input?.Mode === 'passthrough' ? input.Mode : this.mode;
|
|
128
170
|
/** Passthrough mode: upload without encryption */
|
|
129
171
|
if (mode === 'passthrough') {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
172
|
+
try {
|
|
173
|
+
return await this.client.send(new PutObjectCommand({
|
|
174
|
+
...input,
|
|
175
|
+
Bucket: bucket,
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw new Error(`Failed to upload to s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
|
|
180
|
+
}
|
|
134
181
|
}
|
|
135
182
|
/** Encrypt mode: encrypt data before uploading */
|
|
136
183
|
/** Convert input body to Buffer for encryption */
|
|
@@ -149,24 +196,53 @@ export class S3ObjectStore {
|
|
|
149
196
|
}
|
|
150
197
|
/** Use custom encryption if provided */
|
|
151
198
|
if (this.customEncryption) {
|
|
152
|
-
|
|
199
|
+
/** Call custom encryption function with validation */
|
|
200
|
+
let result;
|
|
201
|
+
try {
|
|
202
|
+
result = await this.customEncryption.encrypt(data, this.key);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
throw new Error(`Custom encryption function failed: ${error instanceof Error ? error.message : error}`);
|
|
206
|
+
}
|
|
207
|
+
/** Validate custom encryption result */
|
|
208
|
+
if (!result || typeof result !== 'object') {
|
|
209
|
+
throw new Error('Custom encryption function must return an object with data and metadata.');
|
|
210
|
+
}
|
|
211
|
+
if (!Buffer.isBuffer(result.data)) {
|
|
212
|
+
throw new Error('Custom encryption function must return a Buffer in result.data.');
|
|
213
|
+
}
|
|
214
|
+
if (!result.metadata || typeof result.metadata !== 'object') {
|
|
215
|
+
throw new Error('Custom encryption function must return a metadata object.');
|
|
216
|
+
}
|
|
153
217
|
/** Prefix custom metadata keys to avoid conflicts */
|
|
154
218
|
const customMetadata = {};
|
|
155
219
|
for (const [key, value] of Object.entries(result.metadata)) {
|
|
220
|
+
/** Validate custom metadata keys don't use reserved prefix */
|
|
221
|
+
if (key.startsWith('x-amz-')) {
|
|
222
|
+
throw new Error(`Custom encryption metadata key "${key}" cannot start with "x-amz-" as it is reserved. ` +
|
|
223
|
+
'Please use a different key name.');
|
|
224
|
+
}
|
|
156
225
|
customMetadata[`x-amz-custom-${key}`] = value;
|
|
157
226
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
227
|
+
/** Construct final metadata and validate size */
|
|
228
|
+
const finalMetadata = {
|
|
229
|
+
...(input.Metadata || {}),
|
|
230
|
+
...customMetadata,
|
|
231
|
+
'x-amz-encryption': 'custom',
|
|
232
|
+
};
|
|
233
|
+
validateMetadataSize(finalMetadata);
|
|
234
|
+
try {
|
|
235
|
+
return await this.client.send(new PutObjectCommand({
|
|
236
|
+
...input,
|
|
237
|
+
Bucket: bucket,
|
|
238
|
+
Body: result.data,
|
|
239
|
+
ContentLength: result.data.length,
|
|
240
|
+
Metadata: finalMetadata,
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
throw new Error(`Failed to upload to s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
|
|
245
|
+
}
|
|
170
246
|
}
|
|
171
247
|
/** Default AES-256-GCM encryption */
|
|
172
248
|
/** @type {Buffer} iv - 96-bit IV for GCM mode */
|
|
@@ -174,31 +250,35 @@ export class S3ObjectStore {
|
|
|
174
250
|
/** @type {Buffer} salt - 128-bit salt for scrypt key derivation */
|
|
175
251
|
const salt = randomBytes(16);
|
|
176
252
|
/** Derive encryption key using scrypt (memory-hard, resistant to GPU attacks) */
|
|
177
|
-
const secretKey = await deriveKey(this.key, salt);
|
|
253
|
+
const secretKey = await deriveKey(this.key, salt, this.scryptOptions);
|
|
178
254
|
/** Create AES-256-GCM cipher */
|
|
179
255
|
const cipher = createCipheriv('aes-256-gcm', secretKey, iv);
|
|
180
256
|
/** Encrypt the data */
|
|
181
257
|
const encryptedBody = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
182
258
|
/** Get the GCM authentication tag (ensures data integrity and authenticity) */
|
|
183
259
|
const authTag = cipher.getAuthTag();
|
|
260
|
+
/** Construct final metadata and validate size */
|
|
261
|
+
const finalMetadata = {
|
|
262
|
+
...(input.Metadata || {}),
|
|
263
|
+
'x-amz-iv': iv.toString('base64'),
|
|
264
|
+
'x-amz-salt': salt.toString('base64'),
|
|
265
|
+
'x-amz-auth-tag': authTag.toString('base64'),
|
|
266
|
+
'x-amz-encryption': 'aes-256-gcm',
|
|
267
|
+
};
|
|
268
|
+
validateMetadataSize(finalMetadata);
|
|
184
269
|
/** Upload encrypted data with encryption metadata stored in S3 object metadata */
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
'x-amz-auth-tag': authTag.toString('base64'),
|
|
198
|
-
/** @description Encryption algorithm identifier */
|
|
199
|
-
'x-amz-encryption': 'aes-256-gcm',
|
|
200
|
-
},
|
|
201
|
-
}));
|
|
270
|
+
try {
|
|
271
|
+
return await this.client.send(new PutObjectCommand({
|
|
272
|
+
...input,
|
|
273
|
+
Bucket: bucket,
|
|
274
|
+
Body: encryptedBody,
|
|
275
|
+
ContentLength: encryptedBody.length,
|
|
276
|
+
Metadata: finalMetadata,
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
throw new Error(`Failed to upload to s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
|
|
281
|
+
}
|
|
202
282
|
}
|
|
203
283
|
/**
|
|
204
284
|
* Downloads data from S3 with optional decryption.
|
|
@@ -235,10 +315,24 @@ export class S3ObjectStore {
|
|
|
235
315
|
* ```
|
|
236
316
|
*/
|
|
237
317
|
async download(input) {
|
|
318
|
+
/** Validate required input parameters */
|
|
319
|
+
const bucket = input.Bucket || this.bucket;
|
|
320
|
+
if (!bucket) {
|
|
321
|
+
throw new Error('Bucket is required. Provide it in download() or set a default bucket.');
|
|
322
|
+
}
|
|
323
|
+
if (!input.Key) {
|
|
324
|
+
throw new Error('Key is required for download.');
|
|
325
|
+
}
|
|
238
326
|
/** Fetch the object from S3 */
|
|
239
|
-
|
|
327
|
+
let response;
|
|
328
|
+
try {
|
|
329
|
+
response = await this.client.send(new GetObjectCommand({ ...input, Bucket: bucket }));
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
throw new Error(`Failed to download from s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
|
|
333
|
+
}
|
|
240
334
|
if (!response.Body) {
|
|
241
|
-
throw new Error(
|
|
335
|
+
throw new Error(`No body returned from S3 for s3://${bucket}/${input.Key}`);
|
|
242
336
|
}
|
|
243
337
|
/** Resolve mode: input.mode takes precedence, fallback to client default */
|
|
244
338
|
const mode = input?.Mode === 'encrypt' || input?.Mode === 'passthrough' ? input.Mode : this.mode;
|
|
@@ -270,8 +364,17 @@ export class S3ObjectStore {
|
|
|
270
364
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
271
365
|
}
|
|
272
366
|
const encryptedData = Buffer.concat(chunks);
|
|
273
|
-
/** Decrypt using custom decryption function */
|
|
274
|
-
|
|
367
|
+
/** Decrypt using custom decryption function with validation */
|
|
368
|
+
let decryptedData;
|
|
369
|
+
try {
|
|
370
|
+
decryptedData = await this.customEncryption.decrypt(encryptedData, this.key, customMetadata);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
throw new Error(`Custom decryption function failed for s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
|
|
374
|
+
}
|
|
375
|
+
if (!Buffer.isBuffer(decryptedData)) {
|
|
376
|
+
throw new Error('Custom decryption function must return a Buffer.');
|
|
377
|
+
}
|
|
275
378
|
return {
|
|
276
379
|
Body: Readable.from(decryptedData),
|
|
277
380
|
ContentType: response.ContentType,
|
|
@@ -283,26 +386,43 @@ export class S3ObjectStore {
|
|
|
283
386
|
const ivBase64 = metadata['x-amz-iv'];
|
|
284
387
|
const saltBase64 = metadata['x-amz-salt'];
|
|
285
388
|
const authTagBase64 = metadata['x-amz-auth-tag'];
|
|
286
|
-
/** If encryption metadata is missing,
|
|
389
|
+
/** If encryption metadata is missing, throw error in encrypt mode */
|
|
287
390
|
if (!ivBase64 || !saltBase64 || !authTagBase64 || encryption !== 'aes-256-gcm') {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
ContentLength: response.ContentLength,
|
|
292
|
-
Metadata: response.Metadata,
|
|
293
|
-
};
|
|
391
|
+
throw new Error(`Expected encrypted object but encryption metadata is missing for s3://${input.Bucket}/${input.Key}. ` +
|
|
392
|
+
'This object may not be encrypted or was encrypted with a different method. ' +
|
|
393
|
+
'Use mode: "passthrough" to download unencrypted objects.');
|
|
294
394
|
}
|
|
295
395
|
/** Decode encryption parameters from Base64 */
|
|
296
396
|
const iv = Buffer.from(ivBase64, 'base64');
|
|
297
397
|
const salt = Buffer.from(saltBase64, 'base64');
|
|
298
398
|
const authTag = Buffer.from(authTagBase64, 'base64');
|
|
399
|
+
/** Validate encryption parameter lengths */
|
|
400
|
+
if (iv.length !== 12) {
|
|
401
|
+
throw new Error(`Invalid IV length: expected 12 bytes for GCM mode, got ${iv.length} bytes. ` +
|
|
402
|
+
'The encrypted data may be corrupted.');
|
|
403
|
+
}
|
|
404
|
+
if (salt.length !== 16) {
|
|
405
|
+
throw new Error(`Invalid salt length: expected 16 bytes, got ${salt.length} bytes. ` +
|
|
406
|
+
'The encrypted data may be corrupted.');
|
|
407
|
+
}
|
|
408
|
+
if (authTag.length !== 16) {
|
|
409
|
+
throw new Error(`Invalid authentication tag length: expected 16 bytes, got ${authTag.length} bytes. ` +
|
|
410
|
+
'The encrypted data may be corrupted.');
|
|
411
|
+
}
|
|
299
412
|
/** Derive the decryption key using the same salt used during encryption */
|
|
300
|
-
const secretKey = await deriveKey(this.key, salt);
|
|
413
|
+
const secretKey = await deriveKey(this.key, salt, this.scryptOptions);
|
|
301
414
|
/** Create AES-256-GCM decipher and set the authentication tag */
|
|
302
415
|
const decipher = createDecipheriv('aes-256-gcm', secretKey, iv);
|
|
303
416
|
decipher.setAuthTag(authTag);
|
|
304
417
|
/** Pipe the encrypted stream through the decipher to get decrypted output */
|
|
305
418
|
const decryptedStream = response.Body.pipe(decipher);
|
|
419
|
+
/** Add error handler for decryption stream errors */
|
|
420
|
+
decryptedStream.on('error', (error) => {
|
|
421
|
+
const errorMessage = `Decryption failed for s3://${bucket}/${input.Key}. ` +
|
|
422
|
+
'This may indicate data corruption or wrong encryption key. ' +
|
|
423
|
+
`Error: ${error.message}`;
|
|
424
|
+
decryptedStream.destroy(new Error(errorMessage));
|
|
425
|
+
});
|
|
306
426
|
return {
|
|
307
427
|
Body: decryptedStream,
|
|
308
428
|
ContentType: response.ContentType,
|
|
@@ -311,3 +431,4 @@ export class S3ObjectStore {
|
|
|
311
431
|
};
|
|
312
432
|
}
|
|
313
433
|
}
|
|
434
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module encrypted-s3-store
|
|
8
8
|
* @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
|
|
9
|
-
* @license
|
|
9
|
+
* @license MIT
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* ```typescript
|
|
@@ -24,3 +24,4 @@
|
|
|
24
24
|
*/
|
|
25
25
|
export * from './client.js';
|
|
26
26
|
export * from './types.js';
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module encrypted-s3-store
|
|
8
8
|
* @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
|
|
9
|
-
* @license
|
|
9
|
+
* @license MIT
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* ```typescript
|
|
@@ -24,3 +24,4 @@
|
|
|
24
24
|
*/
|
|
25
25
|
export * from './client.js';
|
|
26
26
|
export * from './types.js';
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -3,22 +3,30 @@
|
|
|
3
3
|
* Contains configuration interfaces and utility types used throughout the library.
|
|
4
4
|
*
|
|
5
5
|
* @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
|
|
6
|
-
* @license
|
|
6
|
+
* @license MIT
|
|
7
7
|
*/
|
|
8
8
|
import { GetObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/client-s3';
|
|
9
|
+
import { Readable } from 'node:stream';
|
|
9
10
|
/**
|
|
10
11
|
* Operation mode for the S3ObjectStore.
|
|
11
12
|
* - 'encrypt': Enables transparent AES-256-GCM encryption for all uploads/downloads
|
|
12
13
|
* - 'passthrough': Data is stored and retrieved without encryption
|
|
13
14
|
*/
|
|
14
15
|
export type ModeType = 'encrypt' | 'passthrough';
|
|
16
|
+
/**
|
|
17
|
+
* Supported body types for upload operations.
|
|
18
|
+
*/
|
|
19
|
+
export type UploadBody = string | Buffer | Uint8Array;
|
|
15
20
|
/**
|
|
16
21
|
* Upload input options extending S3 PutObjectCommandInput.
|
|
17
22
|
* Allows specifying a per-operation encryption mode override.
|
|
18
23
|
*
|
|
19
|
-
* @extends PutObjectCommandInput
|
|
24
|
+
* @extends Omit<PutObjectCommandInput, 'Body'>
|
|
20
25
|
*
|
|
21
|
-
* @property
|
|
26
|
+
* @property Bucket - S3 bucket name (required if not set as default)
|
|
27
|
+
* @property Key - S3 object key (required)
|
|
28
|
+
* @property Body - Content to upload (string, Buffer, or Uint8Array)
|
|
29
|
+
* @property Mode - Optional override for the operation mode. If not specified, uses the client's default mode.
|
|
22
30
|
*
|
|
23
31
|
* @example
|
|
24
32
|
* ```typescript
|
|
@@ -26,11 +34,12 @@ export type ModeType = 'encrypt' | 'passthrough';
|
|
|
26
34
|
* Bucket: 'my-bucket',
|
|
27
35
|
* Key: 'file.txt',
|
|
28
36
|
* Body: 'content',
|
|
29
|
-
*
|
|
37
|
+
* Mode: 'passthrough' // Override to skip encryption for this upload
|
|
30
38
|
* };
|
|
31
39
|
* ```
|
|
32
40
|
*/
|
|
33
|
-
export interface IUpload extends PutObjectCommandInput {
|
|
41
|
+
export interface IUpload extends Omit<PutObjectCommandInput, 'Body'> {
|
|
42
|
+
Body?: UploadBody;
|
|
34
43
|
Mode?: ModeType;
|
|
35
44
|
}
|
|
36
45
|
/**
|
|
@@ -39,20 +48,36 @@ export interface IUpload extends PutObjectCommandInput {
|
|
|
39
48
|
*
|
|
40
49
|
* @extends GetObjectCommandInput
|
|
41
50
|
*
|
|
42
|
-
* @property
|
|
51
|
+
* @property Bucket - S3 bucket name (required if not set as default)
|
|
52
|
+
* @property Key - S3 object key (required)
|
|
53
|
+
* @property Mode - Optional override for the operation mode. If not specified, uses the client's default mode.
|
|
43
54
|
*
|
|
44
55
|
* @example
|
|
45
56
|
* ```typescript
|
|
46
57
|
* const downloadInput: IDownload = {
|
|
47
58
|
* Bucket: 'my-bucket',
|
|
48
59
|
* Key: 'file.txt',
|
|
49
|
-
*
|
|
60
|
+
* Mode: 'passthrough' // Override to skip decryption for this download
|
|
50
61
|
* };
|
|
51
62
|
* ```
|
|
52
63
|
*/
|
|
53
64
|
export interface IDownload extends GetObjectCommandInput {
|
|
54
65
|
Mode?: ModeType;
|
|
55
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Result returned from download operations.
|
|
69
|
+
*
|
|
70
|
+
* @property Body - Readable stream containing decrypted content
|
|
71
|
+
* @property ContentType - MIME type of the content (if available)
|
|
72
|
+
* @property ContentLength - Size of the content in bytes (if available)
|
|
73
|
+
* @property Metadata - S3 object metadata (key-value pairs)
|
|
74
|
+
*/
|
|
75
|
+
export interface DownloadResult {
|
|
76
|
+
Body: Readable;
|
|
77
|
+
ContentType?: string;
|
|
78
|
+
ContentLength?: number;
|
|
79
|
+
Metadata?: Record<string, string>;
|
|
80
|
+
}
|
|
56
81
|
/**
|
|
57
82
|
* Metadata returned by custom encrypt function, stored in S3 object metadata.
|
|
58
83
|
* All values must be strings as they are stored in S3 metadata headers.
|
|
@@ -112,3 +137,28 @@ export interface CustomEncryption {
|
|
|
112
137
|
encrypt: EncryptFunction;
|
|
113
138
|
decrypt: DecryptFunction;
|
|
114
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Scrypt key derivation parameters.
|
|
142
|
+
* Higher values increase security but also increase computation time and memory usage.
|
|
143
|
+
*
|
|
144
|
+
* @property N - CPU/memory cost parameter. Must be a power of 2. Default: 16384 (2^14).
|
|
145
|
+
* Higher values increase resistance to brute-force attacks.
|
|
146
|
+
* @property r - Block size parameter. Default: 8.
|
|
147
|
+
* @property p - Parallelization parameter. Default: 1.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* // For higher security (slower)
|
|
152
|
+
* const store = new S3ObjectStore(s3Client, 'key', 'encrypt', undefined, {
|
|
153
|
+
* N: 65536, // 2^16, 4x more secure than default
|
|
154
|
+
* r: 8,
|
|
155
|
+
* p: 1,
|
|
156
|
+
* });
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export interface ScryptOptions {
|
|
160
|
+
N?: number;
|
|
161
|
+
r?: number;
|
|
162
|
+
p?: number;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,OAAQ,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC;IAClE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,SAAU,SAAQ,qBAAqB;IACtD,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC;AAEpG;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,kBAAkB,KACzB,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ"}
|
package/dist/types.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { ScryptOptions } from './types.js';
|
|
1
2
|
/**
|
|
2
3
|
* Derives a 256-bit encryption key from a password using scrypt.
|
|
3
4
|
*
|
|
4
5
|
* Scrypt is a memory-hard key derivation function that provides strong
|
|
5
6
|
* resistance against brute-force attacks, including GPU-based attacks.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
+
* Default security parameters:
|
|
8
9
|
* - N (cost): 16384 (2^14) - CPU/memory cost parameter
|
|
9
10
|
* - r (blockSize): 8 - block size parameter
|
|
10
11
|
* - p (parallelization): 1 - parallelization parameter
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
*
|
|
13
14
|
* @param key - The user's encryption password/key
|
|
14
15
|
* @param salt - Random salt (should be 16 bytes, stored with encrypted data)
|
|
16
|
+
* @param options - Optional scrypt parameters to override defaults
|
|
15
17
|
* @returns Promise resolving to 32-byte Buffer containing the derived key
|
|
16
18
|
* @throws {Error} If encryption key is empty or not provided
|
|
17
19
|
*
|
|
@@ -20,8 +22,12 @@
|
|
|
20
22
|
* const salt = crypto.randomBytes(16);
|
|
21
23
|
* const derivedKey = await deriveKey('my-secret-password', salt);
|
|
22
24
|
* // derivedKey is a 32-byte Buffer suitable for AES-256
|
|
25
|
+
*
|
|
26
|
+
* // With custom parameters for higher security
|
|
27
|
+
* const derivedKey = await deriveKey('password', salt, { N: 65536 });
|
|
23
28
|
* ```
|
|
24
29
|
*
|
|
25
30
|
* @see https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback
|
|
26
31
|
*/
|
|
27
|
-
export declare const deriveKey: (key: string, salt: Buffer) => Promise<Buffer>;
|
|
32
|
+
export declare const deriveKey: (key: string, salt: Buffer, options?: ScryptOptions) => Promise<Buffer>;
|
|
33
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAU3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,SAAS,GACpB,KAAK,MAAM,EACX,MAAM,MAAM,EACZ,UAAU,aAAa,KACtB,OAAO,CAAC,MAAM,CAkBhB,CAAC"}
|
package/dist/utils.js
CHANGED
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
* @fileoverview Utility functions for encryption key derivation.
|
|
3
3
|
*
|
|
4
4
|
* @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
|
|
5
|
-
* @license
|
|
5
|
+
* @license MIT
|
|
6
6
|
*/
|
|
7
7
|
import { scrypt } from 'node:crypto';
|
|
8
8
|
import { promisify } from 'node:util';
|
|
9
9
|
/** Promisified version of Node.js scrypt function */
|
|
10
10
|
const scryptAsync = promisify(scrypt);
|
|
11
|
+
/** Default scrypt parameters */
|
|
12
|
+
const DEFAULT_SCRYPT_N = 16384; // 2^14
|
|
13
|
+
const DEFAULT_SCRYPT_R = 8;
|
|
14
|
+
const DEFAULT_SCRYPT_P = 1;
|
|
11
15
|
/**
|
|
12
16
|
* Derives a 256-bit encryption key from a password using scrypt.
|
|
13
17
|
*
|
|
14
18
|
* Scrypt is a memory-hard key derivation function that provides strong
|
|
15
19
|
* resistance against brute-force attacks, including GPU-based attacks.
|
|
16
20
|
*
|
|
17
|
-
*
|
|
21
|
+
* Default security parameters:
|
|
18
22
|
* - N (cost): 16384 (2^14) - CPU/memory cost parameter
|
|
19
23
|
* - r (blockSize): 8 - block size parameter
|
|
20
24
|
* - p (parallelization): 1 - parallelization parameter
|
|
@@ -22,6 +26,7 @@ const scryptAsync = promisify(scrypt);
|
|
|
22
26
|
*
|
|
23
27
|
* @param key - The user's encryption password/key
|
|
24
28
|
* @param salt - Random salt (should be 16 bytes, stored with encrypted data)
|
|
29
|
+
* @param options - Optional scrypt parameters to override defaults
|
|
25
30
|
* @returns Promise resolving to 32-byte Buffer containing the derived key
|
|
26
31
|
* @throws {Error} If encryption key is empty or not provided
|
|
27
32
|
*
|
|
@@ -30,24 +35,27 @@ const scryptAsync = promisify(scrypt);
|
|
|
30
35
|
* const salt = crypto.randomBytes(16);
|
|
31
36
|
* const derivedKey = await deriveKey('my-secret-password', salt);
|
|
32
37
|
* // derivedKey is a 32-byte Buffer suitable for AES-256
|
|
38
|
+
*
|
|
39
|
+
* // With custom parameters for higher security
|
|
40
|
+
* const derivedKey = await deriveKey('password', salt, { N: 65536 });
|
|
33
41
|
* ```
|
|
34
42
|
*
|
|
35
43
|
* @see https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback
|
|
36
44
|
*/
|
|
37
|
-
export const deriveKey = async (key, salt) => {
|
|
45
|
+
export const deriveKey = async (key, salt, options) => {
|
|
38
46
|
if (!key) {
|
|
39
47
|
throw new Error('Encryption key is required');
|
|
40
48
|
}
|
|
49
|
+
const N = options?.N ?? DEFAULT_SCRYPT_N;
|
|
50
|
+
const r = options?.r ?? DEFAULT_SCRYPT_R;
|
|
51
|
+
const p = options?.p ?? DEFAULT_SCRYPT_P;
|
|
41
52
|
/**
|
|
42
53
|
* Derive key using scrypt with secure parameters.
|
|
43
|
-
* - N
|
|
44
|
-
* - r
|
|
45
|
-
* - p
|
|
54
|
+
* - N: Memory cost (must be power of 2)
|
|
55
|
+
* - r: Block size
|
|
56
|
+
* - p: Parallelization factor
|
|
46
57
|
*/
|
|
47
|
-
const derivedKey = await scryptAsync(key, salt, 32, {
|
|
48
|
-
N: 16384,
|
|
49
|
-
r: 8,
|
|
50
|
-
p: 1,
|
|
51
|
-
});
|
|
58
|
+
const derivedKey = await scryptAsync(key, salt, 32, { N, r, p });
|
|
52
59
|
return derivedKey;
|
|
53
60
|
};
|
|
61
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,EAAsC,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,qDAAqD;AACrD,MAAM,WAAW,GAAG,SAAS,CAA6D,MAAM,CAAC,CAAC;AAElG,gCAAgC;AAChC,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,OAAO;AACvC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAC3B,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,GAAW,EACX,IAAY,EACZ,OAAuB,EACN,EAAE;IACnB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC,IAAI,gBAAgB,CAAC;IACzC,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC,IAAI,gBAAgB,CAAC;IACzC,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC,IAAI,gBAAgB,CAAC;IAEzC;;;;;OAKG;IACH,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAEjE,OAAO,UAAoB,CAAC;AAC9B,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techbulls/encrypted-s3-store",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Encrypt and decrypt S3 objects with ease",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -11,7 +11,29 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"clean": "rm -rf dist",
|
|
17
|
+
"lint": "eslint .",
|
|
18
|
+
"lint:fix": "eslint . --fix",
|
|
19
|
+
"format": "prettier --write .",
|
|
20
|
+
"format:check": "prettier --check .",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:unit": "vitest run --exclude='**/integration.test.ts'",
|
|
23
|
+
"test:integration": "vitest run src/__tests__/integration.test.ts",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"test:coverage": "vitest run --coverage",
|
|
26
|
+
"prepublishOnly": "npm run clean && npm run lint && npm run test:unit && npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"s3",
|
|
30
|
+
"encryption",
|
|
31
|
+
"aws",
|
|
32
|
+
"aes",
|
|
33
|
+
"security",
|
|
34
|
+
"encrypted",
|
|
35
|
+
"storage"
|
|
36
|
+
],
|
|
15
37
|
"author": "Aditya Chaphekar <aditya.chaphekar@techbulls.com>",
|
|
16
38
|
"license": "MIT",
|
|
17
39
|
"files": [
|
|
@@ -19,27 +41,38 @@
|
|
|
19
41
|
"README.md",
|
|
20
42
|
"LICENSE.md"
|
|
21
43
|
],
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/techbulls/encrypted-s3-store.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/techbulls/encrypted-s3-store/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/techbulls/encrypted-s3-store#readme",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"packageManager": "pnpm@10.19.0",
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@aws-sdk/client-s3": "^3.0.0"
|
|
58
|
+
},
|
|
22
59
|
"devDependencies": {
|
|
60
|
+
"@aws-sdk/client-s3": "^3.958.0",
|
|
61
|
+
"@aws-sdk/types": "^3.957.0",
|
|
23
62
|
"@eslint/js": "^9.39.2",
|
|
63
|
+
"@smithy/util-stream": "^4.5.10",
|
|
24
64
|
"@types/node": "^25.0.3",
|
|
25
65
|
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
26
66
|
"@typescript-eslint/parser": "^8.50.1",
|
|
67
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
68
|
+
"aws-sdk-client-mock": "^4.1.0",
|
|
69
|
+
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
27
70
|
"eslint": "^9.39.2",
|
|
28
71
|
"eslint-config-prettier": "^10.1.8",
|
|
29
72
|
"eslint-plugin-prettier": "^5.5.4",
|
|
30
73
|
"prettier": "^3.7.4",
|
|
31
74
|
"typescript": "^5.9.3",
|
|
32
|
-
"typescript-eslint": "^8.50.1"
|
|
33
|
-
|
|
34
|
-
"dependencies": {
|
|
35
|
-
"@aws-sdk/client-s3": "^3.958.0",
|
|
36
|
-
"@aws-sdk/types": "^3.957.0"
|
|
37
|
-
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "tsc",
|
|
40
|
-
"lint": "eslint .",
|
|
41
|
-
"lint:fix": "eslint . --fix",
|
|
42
|
-
"format": "prettier --write .",
|
|
43
|
-
"format:check": "prettier --check ."
|
|
75
|
+
"typescript-eslint": "^8.50.1",
|
|
76
|
+
"vitest": "^4.0.17"
|
|
44
77
|
}
|
|
45
|
-
}
|
|
78
|
+
}
|