Skip to content

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

License

Notifications You must be signed in to change notification settings

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