@bantis/local-cipher 2.0.0 → 2.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/LICENSE +1 -1
- package/README.md +274 -254
- package/dist/angular/SecureStorageService.d.ts +156 -0
- package/dist/angular.d.ts +9 -0
- package/dist/angular.esm.js +1516 -0
- package/dist/angular.esm.js.map +1 -0
- package/dist/angular.js +1535 -0
- package/dist/angular.js.map +1 -0
- package/dist/core/EncryptionHelper.d.ts +76 -0
- package/dist/core/EventEmitter.d.ts +36 -0
- package/dist/core/KeyRotation.d.ts +24 -0
- package/dist/core/NamespacedStorage.d.ts +39 -0
- package/dist/core/SecureStorage.d.ts +114 -0
- package/dist/index.d.ts +16 -700
- package/dist/index.esm.js +9 -440
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +8 -444
- package/dist/index.js.map +1 -1
- package/dist/react/hooks.d.ts +39 -0
- package/dist/react.d.ts +9 -0
- package/dist/react.esm.js +1414 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/react.js +1440 -0
- package/dist/react.js.map +1 -0
- package/dist/types/index.d.ts +153 -0
- package/dist/utils/compression.d.ts +23 -0
- package/dist/utils/debug.d.ts +18 -0
- package/dist/utils/logger.d.ts +22 -0
- package/package.json +48 -13
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,29 +1,130 @@
|
|
|
1
|
-
# @bantis/local-cipher
|
|
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
|
-
- 🎯 **Sistema de Eventos** - Escucha eventos de cifrado, expiración, errores, etc.
|
|
13
|
-
- 🗜️ **Compresión Automática** - Gzip para valores > 1KB (configurable)
|
|
14
|
-
- ⏰ **Expiración/TTL** - Establece tiempo de vida con auto-limpieza
|
|
15
|
-
- 🔐 **Validación de Integridad** - Checksums SHA-256 automáticos
|
|
16
|
-
- 📦 **Namespaces** - Organiza datos en espacios aislados
|
|
17
|
-
- 🔄 **Rotación de Claves** - Re-encripta datos con nuevas claves
|
|
18
|
-
- 📊 **Modo Debug** - Logging configurable con niveles
|
|
12
|
+
## Problem
|
|
19
13
|
|
|
20
|
-
|
|
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
|
|
21
25
|
|
|
22
26
|
```bash
|
|
23
27
|
npm install @bantis/local-cipher
|
|
24
28
|
```
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
```typescript
|
|
31
|
+
import { SecureStorage } from '@bantis/local-cipher';
|
|
32
|
+
|
|
33
|
+
const storage = SecureStorage.getInstance();
|
|
34
|
+
|
|
35
|
+
// Store encrypted
|
|
36
|
+
await storage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
|
|
37
|
+
|
|
38
|
+
// Retrieve decrypted
|
|
39
|
+
const token = await storage.getItem('token');
|
|
40
|
+
|
|
41
|
+
// Works like localStorage
|
|
42
|
+
await storage.removeItem('token');
|
|
43
|
+
storage.clear();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Before:**
|
|
47
|
+
```
|
|
48
|
+
localStorage: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**After:**
|
|
52
|
+
```
|
|
53
|
+
localStorage: { "__enc_a7f5d8e2": "Qm9keUVuY3J5cHRlZERhdGE..." }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
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
|
|
69
|
+
|
|
70
|
+
## Use Cases
|
|
71
|
+
|
|
72
|
+
### 1. Protect Authentication Tokens
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
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
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Secure User Preferences
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const userStorage = storage.namespace('user');
|
|
91
|
+
await userStorage.setItem('theme', 'dark');
|
|
92
|
+
await userStorage.setItem('language', 'en');
|
|
93
|
+
|
|
94
|
+
// Isolated from other namespaces
|
|
95
|
+
const appStorage = storage.namespace('app');
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Cache Sensitive API Responses
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Store with compression for large data
|
|
102
|
+
const storage = SecureStorage.getInstance({
|
|
103
|
+
storage: { compression: true, compressionThreshold: 512 }
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await storage.setItem('userData', JSON.stringify(largeObject));
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## React Integration
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { useSecureStorage, useSecureStorageEvents } from '@bantis/local-cipher';
|
|
113
|
+
|
|
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
|
+
```
|
|
126
|
+
|
|
127
|
+
## 🚀 Quick Start
|
|
27
128
|
|
|
28
129
|
### JavaScript Vanilla
|
|
29
130
|
|
|
@@ -32,75 +133,44 @@ import { SecureStorage } from '@bantis/local-cipher';
|
|
|
32
133
|
|
|
33
134
|
const storage = SecureStorage.getInstance();
|
|
34
135
|
|
|
35
|
-
//
|
|
136
|
+
// Store encrypted
|
|
36
137
|
await storage.setItem('accessToken', 'mi-token-secreto');
|
|
37
138
|
|
|
38
|
-
//
|
|
139
|
+
// Retrieve decrypted
|
|
39
140
|
const token = await storage.getItem('accessToken');
|
|
40
141
|
|
|
41
|
-
//
|
|
142
|
+
// With expiration (1 hour)
|
|
42
143
|
await storage.setItemWithExpiry('session', sessionData, { expiresIn: 3600000 });
|
|
43
144
|
|
|
44
|
-
//
|
|
145
|
+
// Remove
|
|
45
146
|
await storage.removeItem('accessToken');
|
|
46
147
|
```
|
|
47
148
|
|
|
48
|
-
|
|
149
|
+
**Before:**
|
|
150
|
+
```
|
|
151
|
+
localStorage: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
|
|
152
|
+
```
|
|
49
153
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
iterations: 150000, // PBKDF2 iterations (default: 100000)
|
|
54
|
-
keyLength: 256, // 128, 192, or 256 bits
|
|
55
|
-
saltLength: 16, // Salt size in bytes
|
|
56
|
-
ivLength: 12, // IV size in bytes
|
|
57
|
-
appIdentifier: 'my-app' // Custom app identifier
|
|
58
|
-
},
|
|
59
|
-
storage: {
|
|
60
|
-
compression: true, // Enable compression
|
|
61
|
-
compressionThreshold: 1024, // Compress if > 1KB
|
|
62
|
-
autoCleanup: true, // Auto-clean expired items
|
|
63
|
-
cleanupInterval: 60000 // Cleanup every 60s
|
|
64
|
-
},
|
|
65
|
-
debug: {
|
|
66
|
-
enabled: true, // Enable debug logging
|
|
67
|
-
logLevel: 'verbose', // silent, error, warn, info, debug, verbose
|
|
68
|
-
prefix: 'MyApp' // Log prefix
|
|
69
|
-
}
|
|
70
|
-
});
|
|
154
|
+
**After:**
|
|
155
|
+
```
|
|
156
|
+
localStorage: { "__enc_a7f5d8e2": "Qm9keUVuY3J5cHRlZERhdGE..." }
|
|
71
157
|
```
|
|
72
158
|
|
|
73
159
|
### React
|
|
74
160
|
|
|
75
161
|
```jsx
|
|
76
|
-
import { useSecureStorage
|
|
162
|
+
import { useSecureStorage } from '@bantis/local-cipher/react';
|
|
77
163
|
|
|
78
164
|
function App() {
|
|
79
|
-
// Hook básico
|
|
80
165
|
const [token, setToken, loading] = useSecureStorage('accessToken', '');
|
|
81
|
-
|
|
82
|
-
// Hook con expiración
|
|
83
|
-
const [session, setSession] = useSecureStorageWithExpiry(
|
|
84
|
-
'session',
|
|
85
|
-
null,
|
|
86
|
-
{ expiresIn: 3600000 }
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
// Escuchar eventos
|
|
90
|
-
useSecureStorageEvents('expired', (data) => {
|
|
91
|
-
console.log('Item expired:', data.key);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Usar namespace
|
|
95
|
-
const userStorage = useNamespace('user');
|
|
96
166
|
|
|
97
|
-
if (loading) return <div>
|
|
167
|
+
if (loading) return <div>Loading...</div>;
|
|
98
168
|
|
|
99
169
|
return (
|
|
100
170
|
<div>
|
|
101
171
|
<p>Token: {token}</p>
|
|
102
172
|
<button onClick={() => setToken('nuevo-token')}>
|
|
103
|
-
|
|
173
|
+
Update Token
|
|
104
174
|
</button>
|
|
105
175
|
</div>
|
|
106
176
|
);
|
|
@@ -110,49 +180,103 @@ function App() {
|
|
|
110
180
|
### Angular
|
|
111
181
|
|
|
112
182
|
```typescript
|
|
113
|
-
import { SecureStorageService } from '@bantis/local-cipher';
|
|
183
|
+
import { SecureStorageService } from '@bantis/local-cipher/angular';
|
|
114
184
|
|
|
115
185
|
@Component({
|
|
116
186
|
selector: 'app-root',
|
|
117
|
-
template:
|
|
118
|
-
<div>{{ token$ | async }}</div>
|
|
119
|
-
<button (click)="saveToken()">Guardar</button>
|
|
120
|
-
`
|
|
187
|
+
template: `<div>{{ token$ | async }}</div>`
|
|
121
188
|
})
|
|
122
|
-
export class AppComponent
|
|
189
|
+
export class AppComponent {
|
|
123
190
|
token$ = this.storage.getItem('accessToken');
|
|
124
191
|
|
|
125
192
|
constructor(private storage: SecureStorageService) {}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
193
|
+
|
|
194
|
+
saveToken(token: string) {
|
|
195
|
+
this.storage.setItem('accessToken', token).subscribe();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
## Angular Integration
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { SecureStorageService } from '@bantis/local-cipher';
|
|
203
|
+
|
|
204
|
+
@Component({...})
|
|
205
|
+
export class AppComponent {
|
|
206
|
+
token$ = this.storage.getItem('token');
|
|
207
|
+
|
|
208
|
+
constructor(private storage: SecureStorageService) {
|
|
129
209
|
this.storage.events$.subscribe(event => {
|
|
130
210
|
console.log('Storage event:', event);
|
|
131
211
|
});
|
|
132
|
-
|
|
133
|
-
// Eventos específicos
|
|
134
|
-
this.storage.onEvent$('expired').subscribe(event => {
|
|
135
|
-
console.log('Item expired:', event.key);
|
|
136
|
-
});
|
|
137
212
|
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
138
215
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
216
|
+
## Configuration
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const storage = SecureStorage.getInstance({
|
|
220
|
+
encryption: {
|
|
221
|
+
iterations: 150000, // PBKDF2 iterations
|
|
222
|
+
keyLength: 256, // 128, 192, or 256 bits
|
|
223
|
+
saltLength: 16, // Salt size in bytes
|
|
224
|
+
ivLength: 12, // IV size in bytes
|
|
225
|
+
},
|
|
226
|
+
storage: {
|
|
227
|
+
compression: true, // Enable gzip compression
|
|
228
|
+
compressionThreshold: 1024, // Compress if > 1KB
|
|
229
|
+
autoCleanup: true, // Auto-delete expired items
|
|
230
|
+
cleanupInterval: 60000 // Cleanup every 60s
|
|
231
|
+
},
|
|
232
|
+
debug: {
|
|
233
|
+
enabled: false, // Enable debug logging
|
|
234
|
+
logLevel: 'info' // silent, error, warn, info, debug, verbose
|
|
147
235
|
}
|
|
148
|
-
}
|
|
236
|
+
});
|
|
149
237
|
```
|
|
150
238
|
|
|
151
|
-
##
|
|
239
|
+
## Security
|
|
240
|
+
|
|
241
|
+
### What This Protects Against
|
|
242
|
+
|
|
243
|
+
✅ **XSS attacks** - Encrypted data is useless without the browser-specific key
|
|
244
|
+
✅ **Local file access** - Malware reading browser files gets encrypted data
|
|
245
|
+
✅ **Casual inspection** - DevTools shows encrypted values
|
|
246
|
+
✅ **Data tampering** - Integrity checks detect modifications
|
|
247
|
+
|
|
248
|
+
### What This Does NOT Protect Against
|
|
249
|
+
|
|
250
|
+
❌ **Server-side attacks** - Encryption is client-side only
|
|
251
|
+
❌ **Man-in-the-Middle** - Use HTTPS for data in transit
|
|
252
|
+
❌ **Memory dumps** - Keys exist in memory during runtime
|
|
253
|
+
❌ **Compromised browser** - If the browser is compromised, all bets are off
|
|
254
|
+
❌ **Physical access during active session** - Data is decrypted when accessed
|
|
255
|
+
|
|
256
|
+
### Best Practices
|
|
257
|
+
|
|
258
|
+
1. **Use HTTPS** - Always transmit data over secure connections
|
|
259
|
+
2. **Short TTLs** - Set expiration on sensitive data
|
|
260
|
+
3. **Clear on logout** - Call `storage.clear()` when user logs out
|
|
261
|
+
4. **Monitor events** - Track suspicious activity via event listeners
|
|
262
|
+
5. **Rotate keys** - Periodically call `storage.rotateKeys()`
|
|
263
|
+
6. **Don't store passwords** - Never store plaintext passwords, even encrypted
|
|
264
|
+
|
|
265
|
+
## Browser Support
|
|
152
266
|
|
|
153
|
-
|
|
267
|
+
Requires [Web Crypto API](https://caniuse.com/cryptography):
|
|
154
268
|
|
|
155
|
-
|
|
269
|
+
- Chrome 37+
|
|
270
|
+
- Firefox 34+
|
|
271
|
+
- Safari 11+
|
|
272
|
+
- Edge 12+
|
|
273
|
+
- Opera 24+
|
|
274
|
+
|
|
275
|
+
**Fallback:** Gracefully degrades to unencrypted localStorage in unsupported browsers.
|
|
276
|
+
|
|
277
|
+
## API Reference
|
|
278
|
+
|
|
279
|
+
### Core Methods
|
|
156
280
|
|
|
157
281
|
```typescript
|
|
158
282
|
setItem(key: string, value: string): Promise<void>
|
|
@@ -162,228 +286,124 @@ hasItem(key: string): Promise<boolean>
|
|
|
162
286
|
clear(): void
|
|
163
287
|
```
|
|
164
288
|
|
|
165
|
-
|
|
289
|
+
### Expiration
|
|
166
290
|
|
|
167
291
|
```typescript
|
|
168
|
-
setItemWithExpiry(key: string, value: string, options:
|
|
169
|
-
|
|
292
|
+
setItemWithExpiry(key: string, value: string, options: {
|
|
293
|
+
expiresIn?: number; // milliseconds from now
|
|
294
|
+
expiresAt?: Date; // absolute date
|
|
295
|
+
}): Promise<void>
|
|
170
296
|
|
|
171
|
-
//
|
|
172
|
-
interface ExpiryOptions {
|
|
173
|
-
expiresIn?: number; // Milisegundos desde ahora
|
|
174
|
-
expiresAt?: Date; // Fecha absoluta
|
|
175
|
-
}
|
|
297
|
+
cleanExpired(): Promise<number> // Returns count of deleted items
|
|
176
298
|
```
|
|
177
299
|
|
|
178
|
-
|
|
300
|
+
### Events
|
|
179
301
|
|
|
180
302
|
```typescript
|
|
181
303
|
on(event: StorageEventType, listener: EventListener): void
|
|
182
|
-
once(event: StorageEventType, listener: EventListener): void
|
|
183
304
|
off(event: StorageEventType, listener: EventListener): void
|
|
184
|
-
|
|
305
|
+
once(event: StorageEventType, listener: EventListener): void
|
|
185
306
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
| 'encrypted' | 'decrypted' | 'deleted' | 'cleared'
|
|
189
|
-
| 'expired' | 'error' | 'keyRotated' | 'compressed' | 'decompressed';
|
|
307
|
+
// Event types: 'encrypted', 'decrypted', 'deleted', 'cleared',
|
|
308
|
+
// 'expired', 'error', 'keyRotated', 'compressed'
|
|
190
309
|
```
|
|
191
310
|
|
|
192
|
-
|
|
311
|
+
### Namespaces
|
|
193
312
|
|
|
194
313
|
```typescript
|
|
195
314
|
namespace(name: string): NamespacedStorage
|
|
196
315
|
|
|
197
|
-
// Ejemplo
|
|
198
316
|
const userStorage = storage.namespace('user');
|
|
199
|
-
const sessionStorage = storage.namespace('session');
|
|
200
|
-
|
|
201
317
|
await userStorage.setItem('profile', data);
|
|
202
|
-
await userStorage.clearNamespace();
|
|
318
|
+
await userStorage.clearNamespace();
|
|
203
319
|
```
|
|
204
320
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
verifyIntegrity(key: string): Promise<boolean>
|
|
209
|
-
getIntegrityInfo(key: string): Promise<IntegrityInfo>
|
|
210
|
-
|
|
211
|
-
interface IntegrityInfo {
|
|
212
|
-
valid: boolean;
|
|
213
|
-
lastModified: number;
|
|
214
|
-
checksum: string;
|
|
215
|
-
version: number;
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
#### Rotación de Claves
|
|
321
|
+
### Key Rotation
|
|
220
322
|
|
|
221
323
|
```typescript
|
|
222
324
|
rotateKeys(): Promise<void>
|
|
223
325
|
exportEncryptedData(): Promise<EncryptedBackup>
|
|
224
326
|
importEncryptedData(backup: EncryptedBackup): Promise<void>
|
|
225
|
-
|
|
226
|
-
// Ejemplo
|
|
227
|
-
const backup = await storage.exportEncryptedData();
|
|
228
|
-
await storage.rotateKeys();
|
|
229
|
-
// Si algo sale mal:
|
|
230
|
-
await storage.importEncryptedData(backup);
|
|
231
327
|
```
|
|
232
328
|
|
|
233
|
-
|
|
329
|
+
## FAQ
|
|
234
330
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
cryptoSupported: boolean;
|
|
238
|
-
encryptedKeys: string[];
|
|
239
|
-
unencryptedKeys: string[];
|
|
240
|
-
totalKeys: number;
|
|
241
|
-
config: SecureStorageConfig;
|
|
242
|
-
}
|
|
243
|
-
```
|
|
331
|
+
**Q: Is this secure enough for passwords?**
|
|
332
|
+
A: No. Never store passwords in localStorage, even encrypted. Use secure, httpOnly cookies or sessionStorage with server-side session management.
|
|
244
333
|
|
|
245
|
-
|
|
334
|
+
**Q: Can data be decrypted on another device?**
|
|
335
|
+
A: No. Keys are derived from browser fingerprinting. Data encrypted on Chrome/Windows cannot be decrypted on Firefox/Mac.
|
|
246
336
|
|
|
247
|
-
|
|
337
|
+
**Q: What happens if Web Crypto API is unavailable?**
|
|
338
|
+
A: The library falls back to unencrypted localStorage with a console warning. Check `EncryptionHelper.isSupported()` to detect support.
|
|
248
339
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
await storage.setItemWithExpiry('session', sessionData, {
|
|
252
|
-
expiresIn: 30 * 60 * 1000
|
|
253
|
-
});
|
|
340
|
+
**Q: Does this protect against XSS?**
|
|
341
|
+
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.
|
|
254
342
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
storage: { autoCleanup: true, cleanupInterval: 60000 }
|
|
258
|
-
});
|
|
259
|
-
```
|
|
343
|
+
**Q: How is this different from sessionStorage?**
|
|
344
|
+
A: sessionStorage is cleared on tab close. This provides persistent, encrypted storage across sessions.
|
|
260
345
|
|
|
261
|
-
|
|
346
|
+
**Q: Can I use this in Node.js?**
|
|
347
|
+
A: No. This library requires browser APIs (Web Crypto, localStorage). For Node.js, use native `crypto` module.
|
|
262
348
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const appStorage = storage.namespace('app');
|
|
266
|
-
const tempStorage = storage.namespace('temp');
|
|
349
|
+
**Q: What's the performance impact?**
|
|
350
|
+
A: Encryption adds ~2-5ms per operation. Compression adds ~5-10ms for large values. Negligible for most use cases.
|
|
267
351
|
|
|
268
|
-
|
|
269
|
-
await appStorage.setItem('settings', appSettings);
|
|
270
|
-
await tempStorage.setItem('cache', cacheData);
|
|
352
|
+
## Migration from v1
|
|
271
353
|
|
|
272
|
-
|
|
273
|
-
|
|
354
|
+
v1 data is automatically migrated to v2 format on first read. No action required.
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// v1 and v2 are API-compatible
|
|
358
|
+
const storage = SecureStorage.getInstance(); // Works with both
|
|
274
359
|
```
|
|
275
360
|
|
|
276
|
-
|
|
361
|
+
## Migration from v2.0.x to v2.1.0
|
|
277
362
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
console.log(`✅ Encrypted: ${key}`, metadata);
|
|
281
|
-
});
|
|
363
|
+
> [!WARNING]
|
|
364
|
+
> **Breaking Change:** Framework-specific imports required
|
|
282
365
|
|
|
283
|
-
|
|
284
|
-
console.warn(`⏰ Expired: ${key}`);
|
|
285
|
-
// Refrescar datos o redirigir a login
|
|
286
|
-
});
|
|
366
|
+
### For React Users
|
|
287
367
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
});
|
|
368
|
+
**Before (v2.0.x):**
|
|
369
|
+
```typescript
|
|
370
|
+
import { useSecureStorage } from '@bantis/local-cipher';
|
|
292
371
|
```
|
|
293
372
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
// Rotar claves cada 30 días
|
|
298
|
-
setInterval(async () => {
|
|
299
|
-
console.log('Rotating encryption keys...');
|
|
300
|
-
const backup = await storage.exportEncryptedData();
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
await storage.rotateKeys();
|
|
304
|
-
console.log('Keys rotated successfully');
|
|
305
|
-
} catch (error) {
|
|
306
|
-
console.error('Rotation failed, restoring backup');
|
|
307
|
-
await storage.importEncryptedData(backup);
|
|
308
|
-
}
|
|
309
|
-
}, 30 * 24 * 60 * 60 * 1000);
|
|
373
|
+
**After (v2.1.0):**
|
|
374
|
+
```typescript
|
|
375
|
+
import { useSecureStorage } from '@bantis/local-cipher/react';
|
|
310
376
|
```
|
|
311
377
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
### Cambios Principales
|
|
378
|
+
### For Angular Users
|
|
315
379
|
|
|
316
|
-
**
|
|
317
|
-
```
|
|
318
|
-
|
|
380
|
+
**Before (v2.0.x):**
|
|
381
|
+
```typescript
|
|
382
|
+
import { SecureStorageService } from '@bantis/local-cipher';
|
|
319
383
|
```
|
|
320
384
|
|
|
321
|
-
**
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
const storage = SecureStorage.getInstance();
|
|
325
|
-
|
|
326
|
-
// O con configuración
|
|
327
|
-
const storage = SecureStorage.getInstance({
|
|
328
|
-
encryption: { iterations: 150000 }
|
|
329
|
-
});
|
|
385
|
+
**After (v2.1.0):**
|
|
386
|
+
```typescript
|
|
387
|
+
import { SecureStorageService } from '@bantis/local-cipher/angular';
|
|
330
388
|
```
|
|
331
389
|
|
|
332
|
-
###
|
|
333
|
-
|
|
334
|
-
Los datos de v1 se migran automáticamente al leerlos. No requiere acción del usuario.
|
|
390
|
+
### For Core/Vanilla JS Users
|
|
335
391
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
// Al hacer getItem(), v1 data se detecta y migra automáticamente
|
|
341
|
-
const value = await storage.getItem('oldKey'); // ✅ Migrado a v2
|
|
392
|
+
**No changes required:**
|
|
393
|
+
```typescript
|
|
394
|
+
import { SecureStorage } from '@bantis/local-cipher'; // Still works
|
|
342
395
|
```
|
|
343
396
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
### Protección
|
|
347
|
-
|
|
348
|
-
✅ **XSS** - Datos encriptados incluso si script malicioso accede a localStorage
|
|
349
|
-
✅ **Lectura local** - Malware no puede descifrar sin la clave del navegador
|
|
350
|
-
✅ **Ofuscación** - Nombres de claves encriptados
|
|
351
|
-
✅ **Integridad** - Checksums SHA-256 detectan manipulación
|
|
352
|
-
|
|
353
|
-
### Limitaciones
|
|
354
|
-
|
|
355
|
-
❌ **Servidor** - Encriptación solo cliente
|
|
356
|
-
❌ **MITM** - Usa HTTPS
|
|
357
|
-
❌ **Sesión activa** - Clave en memoria durante uso
|
|
358
|
-
|
|
359
|
-
### Arquitectura
|
|
360
|
-
|
|
361
|
-
1. **Fingerprinting** - Huella única del navegador
|
|
362
|
-
2. **PBKDF2** - 100,000+ iteraciones para derivar clave
|
|
363
|
-
3. **AES-256-GCM** - Cifrado con autenticación
|
|
364
|
-
4. **SHA-256** - Checksums de integridad
|
|
365
|
-
5. **Gzip** - Compresión opcional
|
|
366
|
-
|
|
367
|
-
## 🌐 Compatibilidad
|
|
368
|
-
|
|
369
|
-
- ✅ Chrome 37+
|
|
370
|
-
- ✅ Firefox 34+
|
|
371
|
-
- ✅ Safari 11+
|
|
372
|
-
- ✅ Edge 12+
|
|
373
|
-
- ✅ Opera 24+
|
|
374
|
-
|
|
375
|
-
**Fallback:** En navegadores sin Web Crypto API, usa localStorage normal.
|
|
376
|
-
|
|
377
|
-
## 📄 Licencia
|
|
378
|
-
|
|
379
|
-
MIT © MTT
|
|
380
|
-
|
|
381
|
-
## 🔗 Enlaces
|
|
397
|
+
### Why This Change?
|
|
382
398
|
|
|
383
|
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
399
|
+
v2.0.x bundled all framework code together, causing dependency conflicts:
|
|
400
|
+
- React projects needed Angular dependencies (`@angular/core`, `rxjs`)
|
|
401
|
+
- Vanilla JS projects loaded unused React/Angular code
|
|
402
|
+
- 40% larger bundle size for non-framework users
|
|
386
403
|
|
|
387
|
-
|
|
404
|
+
v2.1.0 separates frameworks into independent bundles:
|
|
405
|
+
- ✅ Core: 42KB (no framework dependencies)
|
|
406
|
+
- ✅ React: 49KB (core + hooks)
|
|
407
|
+
- ✅ Angular: 55KB (core + service)
|
|
388
408
|
|
|
389
|
-
|
|
409
|
+
## FAQ
|