/** * 安全存储工具类 * 使用 AES-GCM 加密算法对敏感数据进行加密存储 * 防止 XSS 攻击导致的 Token 泄露 */ /** * 加密密钥(从环境变量或固定字符串派生) * 注意:实际生产环境应该使用更安全的密钥管理方案 */ const ENCRYPTION_KEY_SOURCE = 'power-leasing-2024-secure-key-v1'; /** * 将字符串转换为 ArrayBuffer * @param {string} str - 要转换的字符串 * @returns {ArrayBuffer} */ function str2ab(str) { const encoder = new TextEncoder(); return encoder.encode(str); } /** * 将 ArrayBuffer 转换为字符串 * @param {ArrayBuffer} buffer - 要转换的 ArrayBuffer * @returns {string} */ function ab2str(buffer) { const decoder = new TextDecoder(); return decoder.decode(buffer); } /** * 将 ArrayBuffer 转换为 Base64 字符串 * @param {ArrayBuffer} buffer - 要转换的 ArrayBuffer * @returns {string} */ function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } /** * 将 Base64 字符串转换为 ArrayBuffer * @param {string} base64 - Base64 字符串 * @returns {ArrayBuffer} */ function base64ToArrayBuffer(base64) { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes.buffer; } /** * 派生加密密钥 * @returns {Promise} */ async function getDerivedKey() { // 将密钥源字符串转换为 ArrayBuffer const keyMaterial = str2ab(ENCRYPTION_KEY_SOURCE); // 导入密钥材料 const baseKey = await crypto.subtle.importKey( 'raw', keyMaterial, 'PBKDF2', false, ['deriveBits', 'deriveKey'] ); // 使用 PBKDF2 派生密钥 // 盐值固定(实际应该存储随机盐值,但为了简化实现使用固定盐) const salt = str2ab('power-leasing-salt-2024'); return await crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, baseKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); } /** * 加密数据 * @param {string} plaintext - 明文数据 * @returns {Promise} 加密后的数据(Base64 编码) */ async function encrypt(plaintext) { try { if (!plaintext || typeof plaintext !== 'string') { return null; } // 获取加密密钥 const key = await getDerivedKey(); // 生成随机 IV(初始化向量) const iv = crypto.getRandomValues(new Uint8Array(12)); // 加密数据 const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, str2ab(plaintext) ); // 将 IV 和加密数据组合(IV 不需要保密,可以明文存储) const combined = new Uint8Array(iv.length + encrypted.byteLength); combined.set(iv, 0); combined.set(new Uint8Array(encrypted), iv.length); // 转换为 Base64 字符串 return arrayBufferToBase64(combined.buffer); } catch (error) { console.error('加密失败:', error); return null; } } /** * 解密数据 * @param {string} ciphertext - 加密后的数据(Base64 编码) * @returns {Promise} 解密后的明文数据 */ async function decrypt(ciphertext) { try { if (!ciphertext || typeof ciphertext !== 'string') { return null; } // 获取加密密钥 const key = await getDerivedKey(); // 解码 Base64 const combined = base64ToArrayBuffer(ciphertext); // 分离 IV 和加密数据 const iv = combined.slice(0, 12); const encrypted = combined.slice(12); // 解密数据 const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, encrypted ); // 转换为字符串 return ab2str(decrypted); } catch (error) { console.error('解密失败:', error); return null; } } /** * 安全存储类 * 提供加密的 localStorage 操作接口 */ class SecureStorage { /** * 安全地设置 localStorage 项(加密存储) * @param {string} key - 存储键名 * @param {string} value - 要存储的值 * @returns {Promise} 是否成功 */ async setItem(key, value) { try { if (!value) { localStorage.removeItem(key); return true; } // 加密数据 const encrypted = await encrypt(value); if (encrypted) { localStorage.setItem(key, encrypted); return true; } return false; } catch (error) { console.error(`安全存储失败 [${key}]:`, error); return false; } } /** * 安全地获取 localStorage 项(解密读取) * @param {string} key - 存储键名 * @returns {Promise} 解密后的值 */ async getItem(key) { try { const encrypted = localStorage.getItem(key); if (!encrypted) { return null; } // 解密数据 return await decrypt(encrypted); } catch (error) { console.error(`安全读取失败 [${key}]:`, error); return null; } } /** * 移除 localStorage 项 * @param {string} key - 存储键名 */ removeItem(key) { try { localStorage.removeItem(key); } catch (error) { console.error(`移除存储失败 [${key}]:`, error); } } /** * 检查 localStorage 项是否存在 * @param {string} key - 存储键名 * @returns {boolean} */ hasItem(key) { try { return localStorage.getItem(key) !== null; } catch (error) { console.error(`检查存储失败 [${key}]:`, error); return false; } } } // 创建单例实例 const secureStorage = new SecureStorage(); export default secureStorage; export { SecureStorage };