Skip to content

arnonsang/secure-local-storage

Repository files navigation

@arnonsang/secure-local-storage

npm version npm downloads License: MIT TypeScript Test Coverage Bundle Size

Zero-config encrypted localStorage replacement using device fingerprinting for automatic key generation.

Features

  • 🔐 Zero Configuration: No setup required, works out of the box
  • 🔒 Device-Bound Encryption: Uses device fingerprinting for automatic key generation
  • 🔄 Backward Compatible: Seamlessly migrates from legacy encryption methods
  • 🚀 Drop-in Replacement: Same API as localStorage but encrypted
  • 🛡️ AES-GCM Encryption: Military-grade encryption with PBKDF2 key derivation
  • 🌐 TypeScript Support: Full TypeScript definitions included
  • 📱 Browser Only: Designed for client-side browser storage

Installation

npm install @arnonsang/secure-local-storage

Usage

Basic Usage (Default/Legacy - No Configuration Needed)

The simplest way to use the library - works exactly like v1.x with enhanced v2.0 security:

import secureLocalStorage from '@arnonsang/secure-local-storage';

// Save encrypted data
await secureLocalStorage.setItem('user-token', 'your-secret-token');

// Retrieve and decrypt data
const token = await secureLocalStorage.getItem('user-token');
console.log(token); // 'your-secret-token'

// Remove encrypted data
secureLocalStorage.removeItem('user-token');

// Clear all encrypted data
secureLocalStorage.clear();

// Check storage info
console.log('Storage length:', secureLocalStorage.length);
console.log('All keys:', secureLocalStorage.keys());

// Get device fingerprint (useful for debugging)
const fingerprint = await secureLocalStorage.getDeviceFingerprint();

✅ This default instance automatically gets v2.0 security enhancements:

  • Dynamic salt generation
  • Enhanced device fingerprinting
  • Secure fallback mechanisms
  • Automatic migration from v1.x data

Advanced Usage (Custom Configuration - v2.0+)

For applications requiring enhanced security or custom settings, create your own configured instance:

import { SecureLocalStorage } from '@arnonsang/secure-local-storage';

// Create a custom configured instance
const customStorage = new SecureLocalStorage({
  customSalt: 'your-unique-app-salt',        // Custom salt for key derivation
  additionalEntropy: 'user-specific-data',   // Extra entropy for fingerprinting
  keyPrefix: 'myapp_',                       // Custom prefix for localStorage keys
  ivSuffix: '_vector',                       // Custom suffix for IV keys
  iterations: 150000,                        // Higher PBKDF2 iterations
  enableLegacySupport: false,                // Disable legacy compatibility
  enableDeprecationWarnings: true           // Show deprecation warnings
});

// Use the custom instance exactly like the default one
await customStorage.setItem('secure-data', 'sensitive-information');
const value = await customStorage.getItem('secure-data');

// Custom instance has all the same methods
console.log('Custom storage length:', customStorage.length);
console.log('Custom storage keys:', customStorage.keys());

// Runtime configuration changes
customStorage.configure({
  additionalEntropy: 'updated-context-data'
});

// Key rotation for enhanced security
await customStorage.rotateKeys();

🔐 Benefits of Custom Configuration:

  • Enhanced Security: Custom salts and entropy for your specific application
  • Isolation: Separate storage instances for different security contexts
  • Flexibility: Adjust security parameters based on your requirements
  • Future-Proof: Easy to enhance security without changing your codebase

Choosing Between Default and Custom Configuration

Feature Default Instance (import default) Custom Instance (new SecureLocalStorage)
Ease of Use ✅ Simplest - zero configuration ⚡ Requires configuration object
Backward Compatibility ✅ 100% compatible with v1.x ✅ Fully compatible
Security Level ✅ Enhanced v2.0 security 🔐 Maximum customizable security
Custom Salts ❌ Uses generated salt ✅ Your own application-specific salt
Multiple Instances ❌ Single global instance ✅ Multiple isolated instances
Key Rotation ✅ Available ✅ Available
Runtime Config ❌ Fixed configuration ✅ Runtime configuration changes

💡 Recommendation:

  • Use Default for: Simple projects, quick prototypes, migrating from v1.x
  • Use Custom for: Production apps, high-security requirements, multi-tenant applications

Usage with React

Option 1: Default Instance (Simplest)

import { useEffect, useState } from 'react';
import secureLocalStorage from '@arnonsang/secure-local-storage';

function useSecureStorage<T>(key: string, defaultValue: T) {
  const [value, setValue] = useState<T>(defaultValue);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const loadValue = async () => {
      try {
        const stored = await secureLocalStorage.getItem(key);
        if (stored !== null) {
          setValue(JSON.parse(stored));
        }
      } catch (error) {
        console.error('Failed to load from secure storage:', error);
      } finally {
        setLoading(false);
      }
    };

    loadValue();
  }, [key]);

  const setStoredValue = async (newValue: T) => {
    try {
      setValue(newValue);
      await secureLocalStorage.setItem(key, JSON.stringify(newValue));
    } catch (error) {
      console.error('Failed to save to secure storage:', error);
    }
  };

  return [value, setStoredValue, loading] as const;
}

// Usage in component
function MyComponent() {
  const [userData, setUserData, loading] = useSecureStorage('user-data', null);

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <pre>{JSON.stringify(userData, null, 2)}</pre>
      <button onClick={() => setUserData({ name: 'John', age: 30 })}>
        Save User Data
      </button>
    </div>
  );
}

Option 2: Custom Configuration (Enhanced Security)

import { useEffect, useState, useContext, createContext } from 'react';
import { SecureLocalStorage } from '@arnonsang/secure-local-storage';

// Create a custom storage instance for your app
const appStorage = new SecureLocalStorage({
  customSalt: 'my-react-app-salt-2024',
  additionalEntropy: 'user-session-context',
  keyPrefix: 'myapp_',
  iterations: 200000 // Higher security
});

// Optional: Create React Context for storage
const StorageContext = createContext(appStorage);

function useCustomSecureStorage<T>(key: string, defaultValue: T) {
  const storage = useContext(StorageContext);
  const [value, setValue] = useState<T>(defaultValue);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const loadValue = async () => {
      try {
        const stored = await storage.getItem(key);
        if (stored !== null) {
          setValue(JSON.parse(stored));
        }
      } catch (error) {
        console.error('Failed to load from secure storage:', error);
      } finally {
        setLoading(false);
      }
    };

    loadValue();
  }, [key, storage]);

  const setStoredValue = async (newValue: T) => {
    try {
      setValue(newValue);
      await storage.setItem(key, JSON.stringify(newValue));
    } catch (error) {
      console.error('Failed to save to secure storage:', error);
    }
  };

  return [value, setStoredValue, loading] as const;
}

Advanced Configuration (v2.0+)

For enhanced security, you can configure the library with custom settings:

import { SecureLocalStorage } from '@arnonsang/secure-local-storage';

const storage = new SecureLocalStorage({
  customSalt: 'your-custom-salt',           // Custom salt for key derivation
  additionalEntropy: 'extra-entropy',      // Additional entropy for fingerprinting
  keyPrefix: 'myapp_',                     // Custom prefix for keys
  ivSuffix: '_vector',                     // Custom suffix for IV keys
  iterations: 150000,                      // PBKDF2 iterations (default: 100000)
  enableLegacySupport: true,               // Enable legacy compatibility (default: true)
  enableDeprecationWarnings: false        // Show deprecation warnings (default: true)
});

// Runtime configuration changes
storage.configure({
  additionalEntropy: 'updated-entropy'
});

// Key rotation for enhanced security
await storage.rotateKeys();

Security Best Practices

// For sensitive applications, consider:
const secureStorage = new SecureLocalStorage({
  customSalt: 'your-unique-application-salt',
  additionalEntropy: 'user-specific-data',
  iterations: 200000, // Higher iterations for more security
});

// Implement periodic key rotation
setInterval(async () => {
  await secureStorage.rotateKeys();
}, 24 * 60 * 60 * 1000); // Rotate daily

How It Works

Encryption Process

  1. Device Fingerprinting: Creates a unique hash based on:

    • Canvas fingerprint
    • Screen properties
    • Browser/navigator information
    • WebGL properties
    • Timezone information
  2. Key Derivation: Uses PBKDF2 with 100,000 iterations to derive encryption keys from device fingerprint

  3. AES-GCM Encryption: Encrypts data using AES-GCM with 256-bit keys and random IVs

  4. Storage: Stores encrypted data and IV separately in localStorage with prefixed keys

Security Features

  • No Stored Keys: Encryption keys are never stored, only derived from device characteristics
  • Device Binding: Data can only be decrypted on the same device it was encrypted on
  • Enhanced Salt Generation: Dynamic salt generation using cryptographically secure random values
  • Custom Entropy Support: Add your own entropy for enhanced security
  • Key Rotation: Rotate encryption keys while preserving data
  • Automatic Migration: Seamlessly upgrades from legacy encryption methods
  • IV Randomization: Each encryption uses a fresh random IV for maximum security
  • Configurable Security: Adjust iterations, salts, and other security parameters

API Reference

Methods

setItem(key: string, value: string): Promise<void>

Encrypts and stores a string value.

getItem(key: string): Promise<string | null>

Retrieves and decrypts a stored value. Returns null if not found or decryption fails.

removeItem(key: string): void

Removes an encrypted item and its associated IV.

clear(): void

Removes all encrypted items managed by secure-local-storage.

hasItem(key: string): Promise<boolean>

Checks if an encrypted item exists and can be decrypted.

keys(): Promise<string[]>

Returns all keys of encrypted items.

getDeviceFingerprint(): Promise<string>

Returns the current device fingerprint (useful for debugging).

configure(config: Partial<SecureStorageConfig>): void

Updates the storage configuration at runtime.

rotateKeys(): Promise<void>

Rotates encryption keys while preserving all stored data.

Properties

length: number

Returns the number of encrypted items stored.

key(index: number): string | null

Returns the key at the specified index.

Browser Compatibility

  • ✅ Chrome 60+
  • ✅ Firefox 55+
  • ✅ Safari 11+
  • ✅ Edge 79+

Requires crypto.subtle API support.

Security Considerations

  • Device-Bound: Data is tied to the device it was encrypted on
  • Re-authentication: Device changes may require users to re-authenticate
  • Client-Side Only: This is designed for client-side storage security
  • Salt Management: Use custom salts for production applications
  • Key Rotation: Implement periodic key rotation for enhanced security
  • Entropy Sources: Consider adding application-specific entropy
  • Legacy Support: Disable legacy support in new applications for better security
  • Not for Sensitive Data: Don't store highly sensitive data that requires server-side encryption

Migration from v1.x

The v2.0 upgrade is automatically backward compatible. Your existing code will continue to work without changes:

// v1.x code - still works in v2.x
import secureLocalStorage from '@arnonsang/secure-local-storage';
await secureLocalStorage.setItem('key', 'value');

To take advantage of new security features:

// v2.x - enhanced security
import { SecureLocalStorage } from '@arnonsang/secure-local-storage';

const storage = new SecureLocalStorage({
  customSalt: 'your-app-salt',
  additionalEntropy: 'user-context'
});

Migration from localStorage

// Before
localStorage.setItem('key', 'value');
const value = localStorage.getItem('key');

// After
await secureLocalStorage.setItem('key', 'value');
const value = await secureLocalStorage.getItem('key');

License

MIT © arnonsang

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

About

Encrypted localStorage wrapper that sits on top of native storage, using device fingerprinting for automatic key generation.

Topics

Resources

License

Stars

Watchers

Forks