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