Front-end encryption storage solution
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