@bantis/local-cipher 1.0.1 → 2.0.1
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/LICENSE +1 -1
- package/README.md +225 -164
- package/dist/index.d.ts +479 -42
- package/dist/index.esm.js +1018 -133
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1028 -132
- package/dist/index.js.map +1 -1
- package/package.json +21 -7
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,253 +1,314 @@
|
|
|
1
1
|
# @bantis/local-cipher
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@bantis/local-cipher)
|
|
4
|
+
[](https://www.npmjs.com/package/@bantis/local-cipher)
|
|
4
5
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://www.typescriptlang.org/)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
**Client-side encryption for localStorage using AES-256-GCM**
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Protect sensitive data in browser storage from XSS attacks, local file access, and casual inspection. Drop-in replacement for localStorage with automatic encryption/decryption.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
- ✅ **Derivación de claves PBKDF2** - 100,000 iteraciones con SHA-256
|
|
13
|
-
- ✅ **Browser Fingerprinting** - Claves únicas por navegador
|
|
14
|
-
- ✅ **Ofuscación de nombres** - Los nombres de las claves también se encriptan
|
|
15
|
-
- ✅ **TypeScript** - Tipado completo
|
|
16
|
-
- ✅ **Framework Agnostic** - Funciona con cualquier proyecto JavaScript
|
|
17
|
-
- ✅ **Integraciones específicas** - Hooks de React y servicio de Angular
|
|
18
|
-
- ✅ **Migración automática** - Convierte datos existentes a formato encriptado
|
|
19
|
-
- ✅ **Fallback transparente** - Funciona en navegadores sin Web Crypto API
|
|
12
|
+
## Problem
|
|
20
13
|
|
|
21
|
-
|
|
14
|
+
localStorage stores data in **plain text**. Anyone with access to DevTools, browser files, or malicious scripts can read:
|
|
15
|
+
- Authentication tokens
|
|
16
|
+
- User credentials
|
|
17
|
+
- API keys
|
|
18
|
+
- Personal information
|
|
19
|
+
|
|
20
|
+
## Solution
|
|
21
|
+
|
|
22
|
+
Transparent AES-256-GCM encryption with browser fingerprinting. Data is encrypted before storage and decrypted on retrieval. Keys are derived from browser characteristics, making data unreadable outside the original browser context.
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
22
25
|
|
|
23
26
|
```bash
|
|
24
|
-
npm install @
|
|
27
|
+
npm install @bantis/local-cipher
|
|
25
28
|
```
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
```typescript
|
|
31
|
+
import { SecureStorage } from '@bantis/local-cipher';
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
const storage = SecureStorage.getInstance();
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
// Store encrypted
|
|
36
|
+
await storage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
|
|
33
37
|
|
|
34
|
-
//
|
|
35
|
-
await
|
|
38
|
+
// Retrieve decrypted
|
|
39
|
+
const token = await storage.getItem('token');
|
|
36
40
|
|
|
37
|
-
//
|
|
38
|
-
|
|
41
|
+
// Works like localStorage
|
|
42
|
+
await storage.removeItem('token');
|
|
43
|
+
storage.clear();
|
|
44
|
+
```
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
**Before:**
|
|
47
|
+
```
|
|
48
|
+
localStorage: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
|
|
49
|
+
```
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
**After:**
|
|
52
|
+
```
|
|
53
|
+
localStorage: { "__enc_a7f5d8e2": "Qm9keUVuY3J5cHRlZERhdGE..." }
|
|
45
54
|
```
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
## Features
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
- ✅ **AES-256-GCM** encryption with authentication
|
|
59
|
+
- ✅ **PBKDF2** key derivation (100k+ iterations)
|
|
60
|
+
- ✅ **Browser fingerprinting** for unique keys per device
|
|
61
|
+
- ✅ **Key obfuscation** - even key names are encrypted
|
|
62
|
+
- ✅ **TTL/Expiration** - auto-delete expired data
|
|
63
|
+
- ✅ **Event system** - monitor storage operations
|
|
64
|
+
- ✅ **Compression** - automatic gzip for large values
|
|
65
|
+
- ✅ **Namespaces** - organize data in isolated spaces
|
|
66
|
+
- ✅ **Integrity checks** - SHA-256 checksums
|
|
67
|
+
- ✅ **TypeScript** - full type definitions
|
|
68
|
+
- ✅ **Framework support** - React hooks, Angular service
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
const [token, setToken, loading] = useSecureStorage('accessToken', '');
|
|
54
|
-
|
|
55
|
-
if (loading) return <div>Cargando...</div>;
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<div>
|
|
59
|
-
<p>Token: {token}</p>
|
|
60
|
-
<button onClick={() => setToken('nuevo-token')}>
|
|
61
|
-
Actualizar Token
|
|
62
|
-
</button>
|
|
63
|
-
</div>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
```
|
|
70
|
+
## Use Cases
|
|
67
71
|
|
|
68
|
-
###
|
|
72
|
+
### 1. Protect Authentication Tokens
|
|
69
73
|
|
|
70
74
|
```typescript
|
|
71
|
-
|
|
75
|
+
// Store JWT with 1-hour expiration
|
|
76
|
+
await storage.setItemWithExpiry('accessToken', jwt, {
|
|
77
|
+
expiresIn: 3600000
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Auto-cleanup expired tokens
|
|
81
|
+
storage.on('expired', ({ key }) => {
|
|
82
|
+
console.log(`Token ${key} expired, redirecting to login`);
|
|
83
|
+
window.location.href = '/login';
|
|
84
|
+
});
|
|
85
|
+
```
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
selector: 'app-root',
|
|
75
|
-
template: `<div>{{ token$ | async }}</div>`
|
|
76
|
-
})
|
|
77
|
-
export class AppComponent {
|
|
78
|
-
token$ = this.secureStorage.getItem('accessToken');
|
|
87
|
+
### 2. Secure User Preferences
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
```typescript
|
|
90
|
+
const userStorage = storage.namespace('user');
|
|
91
|
+
await userStorage.setItem('theme', 'dark');
|
|
92
|
+
await userStorage.setItem('language', 'en');
|
|
81
93
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
}
|
|
94
|
+
// Isolated from other namespaces
|
|
95
|
+
const appStorage = storage.namespace('app');
|
|
86
96
|
```
|
|
87
97
|
|
|
88
|
-
|
|
98
|
+
### 3. Cache Sensitive API Responses
|
|
89
99
|
|
|
90
|
-
|
|
100
|
+
```typescript
|
|
101
|
+
// Store with compression for large data
|
|
102
|
+
const storage = SecureStorage.getInstance({
|
|
103
|
+
storage: { compression: true, compressionThreshold: 512 }
|
|
104
|
+
});
|
|
91
105
|
|
|
92
|
-
|
|
106
|
+
await storage.setItem('userData', JSON.stringify(largeObject));
|
|
107
|
+
```
|
|
93
108
|
|
|
94
|
-
|
|
95
|
-
Guarda un valor encriptado en localStorage.
|
|
109
|
+
## React Integration
|
|
96
110
|
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
```tsx
|
|
112
|
+
import { useSecureStorage, useSecureStorageEvents } from '@bantis/local-cipher';
|
|
99
113
|
|
|
100
|
-
|
|
101
|
-
|
|
114
|
+
function App() {
|
|
115
|
+
const [token, setToken, loading] = useSecureStorage('token', '');
|
|
116
|
+
|
|
117
|
+
useSecureStorageEvents('expired', () => {
|
|
118
|
+
// Handle expiration
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (loading) return <div>Loading...</div>;
|
|
122
|
+
|
|
123
|
+
return <div>Token: {token}</div>;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
102
126
|
|
|
103
|
-
|
|
104
|
-
Verifica si existe una clave.
|
|
127
|
+
## Angular Integration
|
|
105
128
|
|
|
106
|
-
|
|
107
|
-
|
|
129
|
+
```typescript
|
|
130
|
+
import { SecureStorageService } from '@bantis/local-cipher';
|
|
108
131
|
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
@Component({...})
|
|
133
|
+
export class AppComponent {
|
|
134
|
+
token$ = this.storage.getItem('token');
|
|
111
135
|
|
|
112
|
-
|
|
136
|
+
constructor(private storage: SecureStorageService) {
|
|
137
|
+
this.storage.events$.subscribe(event => {
|
|
138
|
+
console.log('Storage event:', event);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
113
143
|
|
|
114
|
-
|
|
115
|
-
Hook principal para usar SecureStorage de forma reactiva.
|
|
144
|
+
## Configuration
|
|
116
145
|
|
|
117
|
-
```
|
|
118
|
-
const
|
|
146
|
+
```typescript
|
|
147
|
+
const storage = SecureStorage.getInstance({
|
|
148
|
+
encryption: {
|
|
149
|
+
iterations: 150000, // PBKDF2 iterations
|
|
150
|
+
keyLength: 256, // 128, 192, or 256 bits
|
|
151
|
+
saltLength: 16, // Salt size in bytes
|
|
152
|
+
ivLength: 12, // IV size in bytes
|
|
153
|
+
},
|
|
154
|
+
storage: {
|
|
155
|
+
compression: true, // Enable gzip compression
|
|
156
|
+
compressionThreshold: 1024, // Compress if > 1KB
|
|
157
|
+
autoCleanup: true, // Auto-delete expired items
|
|
158
|
+
cleanupInterval: 60000 // Cleanup every 60s
|
|
159
|
+
},
|
|
160
|
+
debug: {
|
|
161
|
+
enabled: false, // Enable debug logging
|
|
162
|
+
logLevel: 'info' // silent, error, warn, info, debug, verbose
|
|
163
|
+
}
|
|
164
|
+
});
|
|
119
165
|
```
|
|
120
166
|
|
|
121
|
-
|
|
167
|
+
## Security
|
|
122
168
|
|
|
123
|
-
|
|
124
|
-
Verifica si existe una clave.
|
|
169
|
+
### What This Protects Against
|
|
125
170
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
171
|
+
✅ **XSS attacks** - Encrypted data is useless without the browser-specific key
|
|
172
|
+
✅ **Local file access** - Malware reading browser files gets encrypted data
|
|
173
|
+
✅ **Casual inspection** - DevTools shows encrypted values
|
|
174
|
+
✅ **Data tampering** - Integrity checks detect modifications
|
|
129
175
|
|
|
130
|
-
|
|
176
|
+
### What This Does NOT Protect Against
|
|
131
177
|
|
|
132
|
-
|
|
133
|
-
|
|
178
|
+
❌ **Server-side attacks** - Encryption is client-side only
|
|
179
|
+
❌ **Man-in-the-Middle** - Use HTTPS for data in transit
|
|
180
|
+
❌ **Memory dumps** - Keys exist in memory during runtime
|
|
181
|
+
❌ **Compromised browser** - If the browser is compromised, all bets are off
|
|
182
|
+
❌ **Physical access during active session** - Data is decrypted when accessed
|
|
134
183
|
|
|
135
|
-
|
|
136
|
-
const debugInfo = useSecureStorageDebug();
|
|
137
|
-
console.log(`Claves encriptadas: ${debugInfo.encryptedKeys.length}`);
|
|
138
|
-
```
|
|
184
|
+
### Best Practices
|
|
139
185
|
|
|
140
|
-
|
|
186
|
+
1. **Use HTTPS** - Always transmit data over secure connections
|
|
187
|
+
2. **Short TTLs** - Set expiration on sensitive data
|
|
188
|
+
3. **Clear on logout** - Call `storage.clear()` when user logs out
|
|
189
|
+
4. **Monitor events** - Track suspicious activity via event listeners
|
|
190
|
+
5. **Rotate keys** - Periodically call `storage.rotateKeys()`
|
|
191
|
+
6. **Don't store passwords** - Never store plaintext passwords, even encrypted
|
|
141
192
|
|
|
142
|
-
|
|
193
|
+
## Browser Support
|
|
143
194
|
|
|
144
|
-
|
|
145
|
-
// Inyectar el servicio
|
|
146
|
-
constructor(private secureStorage: SecureStorageService) {}
|
|
195
|
+
Requires [Web Crypto API](https://caniuse.com/cryptography):
|
|
147
196
|
|
|
148
|
-
|
|
149
|
-
|
|
197
|
+
- Chrome 37+
|
|
198
|
+
- Firefox 34+
|
|
199
|
+
- Safari 11+
|
|
200
|
+
- Edge 12+
|
|
201
|
+
- Opera 24+
|
|
150
202
|
|
|
151
|
-
|
|
152
|
-
this.secureStorage.getItem('key').subscribe(value => console.log(value));
|
|
203
|
+
**Fallback:** Gracefully degrades to unencrypted localStorage in unsupported browsers.
|
|
153
204
|
|
|
154
|
-
|
|
155
|
-
this.secureStorage.setObject('user', { id: 1, name: 'Juan' }).subscribe();
|
|
205
|
+
## API Reference
|
|
156
206
|
|
|
157
|
-
|
|
158
|
-
this.secureStorage.getObject<User>('user').subscribe(user => console.log(user));
|
|
207
|
+
### Core Methods
|
|
159
208
|
|
|
160
|
-
|
|
161
|
-
|
|
209
|
+
```typescript
|
|
210
|
+
setItem(key: string, value: string): Promise<void>
|
|
211
|
+
getItem(key: string): Promise<string | null>
|
|
212
|
+
removeItem(key: string): Promise<void>
|
|
213
|
+
hasItem(key: string): Promise<boolean>
|
|
214
|
+
clear(): void
|
|
162
215
|
```
|
|
163
216
|
|
|
164
|
-
|
|
217
|
+
### Expiration
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
setItemWithExpiry(key: string, value: string, options: {
|
|
221
|
+
expiresIn?: number; // milliseconds from now
|
|
222
|
+
expiresAt?: Date; // absolute date
|
|
223
|
+
}): Promise<void>
|
|
224
|
+
|
|
225
|
+
cleanExpired(): Promise<number> // Returns count of deleted items
|
|
226
|
+
```
|
|
165
227
|
|
|
166
|
-
|
|
228
|
+
### Events
|
|
167
229
|
|
|
168
|
-
```
|
|
169
|
-
|
|
230
|
+
```typescript
|
|
231
|
+
on(event: StorageEventType, listener: EventListener): void
|
|
232
|
+
off(event: StorageEventType, listener: EventListener): void
|
|
233
|
+
once(event: StorageEventType, listener: EventListener): void
|
|
170
234
|
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
'accessToken',
|
|
174
|
-
'refreshToken',
|
|
175
|
-
'user',
|
|
176
|
-
'sessionId'
|
|
177
|
-
]);
|
|
235
|
+
// Event types: 'encrypted', 'decrypted', 'deleted', 'cleared',
|
|
236
|
+
// 'expired', 'error', 'keyRotated', 'compressed'
|
|
178
237
|
```
|
|
179
238
|
|
|
180
|
-
|
|
239
|
+
### Namespaces
|
|
181
240
|
|
|
182
|
-
|
|
241
|
+
```typescript
|
|
242
|
+
namespace(name: string): NamespacedStorage
|
|
183
243
|
|
|
184
|
-
|
|
244
|
+
const userStorage = storage.namespace('user');
|
|
245
|
+
await userStorage.setItem('profile', data);
|
|
246
|
+
await userStorage.clearNamespace();
|
|
247
|
+
```
|
|
185
248
|
|
|
186
|
-
|
|
187
|
-
✅ **Lectura de archivos locales** - Malware que lee archivos del navegador no puede descifrar los datos
|
|
188
|
-
✅ **Ofuscación** - Los nombres de las claves también están encriptados
|
|
249
|
+
### Key Rotation
|
|
189
250
|
|
|
190
|
-
|
|
251
|
+
```typescript
|
|
252
|
+
rotateKeys(): Promise<void>
|
|
253
|
+
exportEncryptedData(): Promise<EncryptedBackup>
|
|
254
|
+
importEncryptedData(backup: EncryptedBackup): Promise<void>
|
|
255
|
+
```
|
|
191
256
|
|
|
192
|
-
|
|
193
|
-
❌ **Man-in-the-Middle** - Usa HTTPS para proteger datos en tránsito
|
|
194
|
-
❌ **Acceso físico durante sesión activa** - Si el navegador está abierto, la clave está en memoria
|
|
257
|
+
## FAQ
|
|
195
258
|
|
|
196
|
-
|
|
259
|
+
**Q: Is this secure enough for passwords?**
|
|
260
|
+
A: No. Never store passwords in localStorage, even encrypted. Use secure, httpOnly cookies or sessionStorage with server-side session management.
|
|
197
261
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
3. **Cifrado AES-GCM** - Cada valor se encripta con un IV aleatorio único
|
|
201
|
-
4. **Almacenamiento** - Se guarda `IV + datos encriptados` en Base64
|
|
262
|
+
**Q: Can data be decrypted on another device?**
|
|
263
|
+
A: No. Keys are derived from browser fingerprinting. Data encrypted on Chrome/Windows cannot be decrypted on Firefox/Mac.
|
|
202
264
|
|
|
203
|
-
**
|
|
265
|
+
**Q: What happens if Web Crypto API is unavailable?**
|
|
266
|
+
A: The library falls back to unencrypted localStorage with a console warning. Check `EncryptionHelper.isSupported()` to detect support.
|
|
204
267
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
208
|
-
user: '{"id":1,"name":"Juan"}'
|
|
268
|
+
**Q: Does this protect against XSS?**
|
|
269
|
+
A: Partially. It makes stolen data harder to use, but XSS can still intercept data when it's decrypted in memory. Use CSP headers and sanitize inputs.
|
|
209
270
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
__enc_9c3e7b1a5f8d: "QW5vdGhlckVuY3J5cHRlZA..."
|
|
213
|
-
__app_salt: "cmFuZG9tU2FsdEhlcmU="
|
|
214
|
-
```
|
|
271
|
+
**Q: How is this different from sessionStorage?**
|
|
272
|
+
A: sessionStorage is cleared on tab close. This provides persistent, encrypted storage across sessions.
|
|
215
273
|
|
|
216
|
-
|
|
274
|
+
**Q: Can I use this in Node.js?**
|
|
275
|
+
A: No. This library requires browser APIs (Web Crypto, localStorage). For Node.js, use native `crypto` module.
|
|
217
276
|
|
|
218
|
-
|
|
219
|
-
|
|
277
|
+
**Q: What's the performance impact?**
|
|
278
|
+
A: Encryption adds ~2-5ms per operation. Compression adds ~5-10ms for large values. Negligible for most use cases.
|
|
220
279
|
|
|
221
|
-
|
|
222
|
-
await debugEncryptionState();
|
|
280
|
+
## Migration from v1
|
|
223
281
|
|
|
224
|
-
|
|
225
|
-
await testEncryption();
|
|
282
|
+
v1 data is automatically migrated to v2 format on first read. No action required.
|
|
226
283
|
|
|
227
|
-
|
|
228
|
-
|
|
284
|
+
```typescript
|
|
285
|
+
// v1 and v2 are API-compatible
|
|
286
|
+
const storage = SecureStorage.getInstance(); // Works with both
|
|
229
287
|
```
|
|
230
288
|
|
|
231
|
-
##
|
|
289
|
+
## Examples
|
|
232
290
|
|
|
233
|
-
|
|
291
|
+
See [/examples](./examples) directory for:
|
|
292
|
+
- Basic usage
|
|
293
|
+
- React integration
|
|
294
|
+
- Angular integration
|
|
295
|
+
- Advanced features (TTL, events, namespaces)
|
|
234
296
|
|
|
235
|
-
|
|
236
|
-
- ✅ Firefox 34+
|
|
237
|
-
- ✅ Safari 11+
|
|
238
|
-
- ✅ Edge 12+
|
|
239
|
-
- ✅ Opera 24+
|
|
297
|
+
## Contributing
|
|
240
298
|
|
|
241
|
-
|
|
299
|
+
Contributions welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) first.
|
|
242
300
|
|
|
243
|
-
##
|
|
301
|
+
## Security Issues
|
|
244
302
|
|
|
245
|
-
|
|
303
|
+
Report security vulnerabilities to [security@example.com](mailto:security@example.com). See [SECURITY.md](./SECURITY.md) for details.
|
|
246
304
|
|
|
247
|
-
##
|
|
305
|
+
## License
|
|
248
306
|
|
|
249
|
-
|
|
307
|
+
MIT © MTT - See [LICENSE](./LICENSE) for details.
|
|
250
308
|
|
|
251
|
-
##
|
|
309
|
+
## Links
|
|
252
310
|
|
|
253
|
-
|
|
311
|
+
- [npm package](https://www.npmjs.com/package/@bantis/local-cipher)
|
|
312
|
+
- [GitHub repository](https://github.com/master-tech-team/-bantis-local-cipher)
|
|
313
|
+
- [Changelog](./CHANGELOG.md)
|
|
314
|
+
- [Security Policy](./SECURITY.md)
|