阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 前端加密存储方案

前端加密存储方案

作者:陈川 阅读数:10256人阅读 分类: 前端安全

前端加密存储的必要性

数据泄露事件频发,前端作为用户数据的第一个接触点,加密存储成为必备防护手段。浏览器环境存在多种数据存储方式,但默认都不具备加密能力,敏感信息如用户凭证、个人隐私等直接存储会带来严重安全隐患。

常见前端存储方式与风险

localStorage/sessionStorage

// 危险示例:明文存储敏感信息
localStorage.setItem('authToken', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');

这种存储方式存在XSS攻击风险,攻击者注入的恶意脚本可直接读取全部存储内容。

Cookie

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

即使设置了HttpOnly标志,仍然可能通过中间人攻击获取。敏感Cookie应当设置Secure和SameSite属性。

IndexedDB

虽然可以存储结构化数据,但同样以明文形式保存:

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'});
};

客户端加密技术选型

Web Crypto API

现代浏览器原生支持的加密接口:

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

SJCL(Stanford JavaScript Crypto Library)

轻量级加密库示例:

const sjcl = require('sjcl');
const ciphertext = sjcl.encrypt("password", "敏感数据");
console.log(sjcl.decrypt("password", ciphertext));

Libsodium.js

更强大的加密方案:

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("数据明文", nonce, key);
})();

完整加密存储实现方案

密钥管理策略

// 基于用户密码派生密钥
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']
  );
}

数据加密存储流程

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()
  }));
}

加密数据读取流程

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);
}

特殊场景处理方案

大文件分块加密

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);
  });
}

离线环境处理

// 使用Web Worker进行后台加密
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);
  }
};

安全增强措施

内存清理策略

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

// 使用后立即清理敏感数据
const sensitiveData = new ArrayBuffer(1024);
// ...使用数据...
wipeMemory(sensitiveData);

防篡改机制

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)) };
}

性能优化技巧

密钥缓存策略

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); // 5分钟后清除
  return derivedKey;
}

WebAssembly加速

// 使用Rust编写加密逻辑并通过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()
}

浏览器兼容性方案

特性检测与降级策略

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

实际应用案例

加密表单数据存储

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);
    }
    
    // 发送加密数据到服务器
    fetch('/submit', {
      method: 'POST',
      body: JSON.stringify(encryptedData)
    });
  }
}

加密IndexedDB包装器

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) {
    // 实现加密逻辑
  }
}

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

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌