Files
webs/power_leasing/src/utils/secureStorage.js
2026-01-09 17:13:29 +08:00

258 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 安全存储工具类
* 使用 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<CryptoKey>}
*/
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<string>} 加密后的数据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<string|null>} 解密后的明文数据
*/
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<boolean>} 是否成功
*/
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<string|null>} 解密后的值
*/
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 };