阿里云主机折上折
  • 微信号
Current Site:Index > Front-end encryption storage solution

Front-end encryption storage solution

Author:Chuan Chen 阅读数:10255人阅读 分类: 前端安全

The Necessity of Front-end Encryption Storage

With frequent data breaches, the front-end, as the first point of contact for user data, requires encryption storage as an essential protective measure. Browser environments offer various data storage methods, but none inherently provide encryption capabilities. Sensitive information such as user credentials and personal privacy data stored directly poses severe security risks.

Common Front-end Storage Methods and Risks

localStorage/sessionStorage

// Dangerous example: Storing sensitive information in plaintext
localStorage.setItem('authToken', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');

This storage method is vulnerable to XSS attacks, where injected malicious scripts can directly read all stored content.

Cookie

document.cookie = "username=admin; path=/";

Even with the HttpOnly flag set, cookies can still be intercepted via man-in-the-middle attacks. Sensitive cookies should include Secure and SameSite attributes.

IndexedDB

While capable of storing structured data, it also saves in plaintext:

const request = indexedDB.open('userDB');
request.onsuccess = (e) => {
  const db = e.target.result;
  const tx = db.transaction('users', 'readwrite');
  tx.objectStore('users').put({id: 1, creditCard: '4111111111111111'});
};

Client-Side Encryption Technology Options

Web Crypto API

A native encryption interface supported by modern browsers:

async function generateKey() {
  return await window.crypto.subtle.generateKey(
    { name: "AES-GCM", length: 256 },
    true,
    ["encrypt", "decrypt"]
  );
}

SJCL (Stanford JavaScript Crypto Library)

Example of a lightweight encryption library:

const sjcl = require('sjcl');
const ciphertext = sjcl.encrypt("password", "Sensitive data");
console.log(sjcl.decrypt("password", ciphertext));

Libsodium.js

A more robust encryption solution:

const _sodium = require('libsodium-wrappers');
(async() => {
  await _sodium.ready;
  const sodium = _sodium;
  const key = sodium.crypto_secretbox_keygen();
  const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
  const ciphertext = sodium.crypto_secretbox_easy("Plaintext data", nonce, key);
})();

Complete Encryption Storage Implementation

Key Management Strategy

// Derive a key based on the user's password
async function deriveKey(password, salt) {
  const keyMaterial = await window.crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(password),
    { name: 'PBKDF2' },
    false,
    ['deriveKey']
  );
  
  return await window.crypto.subtle.deriveKey(
    { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
    keyMaterial,
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt']
  );
}

Data Encryption Storage Process

async function secureStorageSet(key, value) {
  const salt = window.crypto.getRandomValues(new Uint8Array(16));
  const iv = window.crypto.getRandomValues(new Uint8Array(12));
  const cryptoKey = await deriveKey(userPassword, salt);
  
  const ciphertext = await window.crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    cryptoKey,
    new TextEncoder().encode(value)
  );
  
  localStorage.setItem(key, JSON.stringify({
    salt: Array.from(salt).toString(),
    iv: Array.from(iv).toString(),
    ciphertext: Array.from(new Uint8Array(ciphertext)).toString()
  }));
}

Encrypted Data Retrieval Process

async function secureStorageGet(key) {
  const storedData = JSON.parse(localStorage.getItem(key));
  const salt = Uint8Array.from(storedData.salt.split(',').map(Number));
  const iv = Uint8Array.from(storedData.iv.split(',').map(Number));
  const ciphertext = Uint8Array.from(storedData.ciphertext.split(',').map(Number));
  
  const cryptoKey = await deriveKey(userPassword, salt);
  const decrypted = await window.crypto.subtle.decrypt(
    { name: "AES-GCM", iv },
    cryptoKey,
    ciphertext
  );
  
  return new TextDecoder().decode(decrypted);
}

Special Scenario Solutions

Large File Chunk Encryption

async function encryptFile(file, key) {
  const CHUNK_SIZE = 16384; // 16KB
  const reader = new FileReader();
  let encryptedChunks = [];
  
  return new Promise((resolve) => {
    reader.onload = async (e) => {
      const buffer = new Uint8Array(e.target.result);
      for (let i = 0; i < buffer.length; i += CHUNK_SIZE) {
        const chunk = buffer.slice(i, i + CHUNK_SIZE);
        const iv = window.crypto.getRandomValues(new Uint8Array(12));
        const encrypted = await window.crypto.subtle.encrypt(
          { name: "AES-GCM", iv },
          key,
          chunk
        );
        encryptedChunks.push({ iv, data: new Uint8Array(encrypted) });
      }
      resolve(encryptedChunks);
    };
    reader.readAsArrayBuffer(file);
  });
}

Offline Environment Handling

// Use Web Workers for background encryption
const cryptoWorker = new Worker('crypto-worker.js');
cryptoWorker.postMessage({
  type: 'ENCRYPT',
  payload: { data: largeData, key: derivedKey }
});

// crypto-worker.js
self.onmessage = async (e) => {
  if (e.data.type === 'ENCRYPT') {
    const result = await encryptData(e.data.payload);
    self.postMessage(result);
  }
};

Security Enhancement Measures

Memory Wiping Strategy

function wipeMemory(buffer) {
  const view = new Uint8Array(buffer);
  for (let i = 0; i < view.length; i++) {
    view[i] = 0;
  }
}

// Immediately wipe sensitive data after use
const sensitiveData = new ArrayBuffer(1024);
// ...Use the data...
wipeMemory(sensitiveData);

Tamper-Proof Mechanism

async function addHMAC(ciphertext, key) {
  const hmacKey = await window.crypto.subtle.importKey(
    'raw',
    key,
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  );
  
  const signature = await window.crypto.subtle.sign(
    'HMAC',
    hmacKey,
    ciphertext
  );
  
  return { ciphertext, signature: Array.from(new Uint8Array(signature)) };
}

Performance Optimization Techniques

Key Caching Strategy

const keyCache = new Map();

async function getCachedKey(password, salt) {
  const cacheKey = `${password}-${Array.from(salt).join()}`;
  if (keyCache.has(cacheKey)) {
    return keyCache.get(cacheKey);
  }
  
  const derivedKey = await deriveKey(password, salt);
  keyCache.set(cacheKey, derivedKey);
  setTimeout(() => keyCache.delete(cacheKey), 300000); // Clear after 5 minutes
  return derivedKey;
}

WebAssembly Acceleration

// Write encryption logic in Rust and compile with wasm-pack
#[wasm_bindgen]
pub fn aes_encrypt(data: &[u8], key: &[u8], iv: &[u8]) -> Vec<u8> {
  use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead};
  let cipher = Aes256Gcm::new_from_slice(key).unwrap();
  cipher.encrypt(iv.into(), data).unwrap()
}

Browser Compatibility Solutions

Feature Detection and Fallback Strategy

function getCrypto() {
  if (window.crypto && window.crypto.subtle) {
    return window.crypto.subtle;
  }
  
  if (window.msCrypto && window.msCrypto.subtle) {
    return window.msCrypto.subtle;
  }
  
  // Fallback to SJCL
  return {
    encrypt: (algorithm, key, data) => {
      return new Promise(resolve => {
        const result = sjcl.encrypt(key, data);
        resolve(result);
      });
    }
  };
}

Practical Application Examples

Encrypted Form Data Storage

class SecureFormStorage {
  constructor(formSelector, secret) {
    this.form = document.querySelector(formSelector);
    this.secret = secret;
    this.form.addEventListener('submit', this.encryptBeforeSubmit.bind(this));
  }
  
  async encryptBeforeSubmit(e) {
    e.preventDefault();
    const formData = new FormData(this.form);
    const encryptedData = {};
    
    for (let [key, value] of formData.entries()) {
      encryptedData[key] = await secureStorageSet(key, value);
    }
    
    // Send encrypted data to the server
    fetch('/submit', {
      method: 'POST',
      body: JSON.stringify(encryptedData)
    });
  }
}

Encrypted IndexedDB Wrapper

class EncryptedDB {
  constructor(dbName, version, encryptionKey) {
    this.dbName = dbName;
    this.version = version;
    this.key = encryptionKey;
  }
  
  async put(storeName, data) {
    const db = await this._openDB();
    const encrypted = await this._encryptData(data);
    
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, 'readwrite');
      tx.oncomplete = () => resolve();
      tx.onerror = (e) => reject(e.target.error);
      tx.objectStore(storeName).put(encrypted);
    });
  }
  
  async _encryptData(data) {
    // Implement encryption logic
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.