每周五更新
This commit is contained in:
257
power_leasing/src/utils/secureStorage.js
Normal file
257
power_leasing/src/utils/secureStorage.js
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* 安全存储工具类
|
||||
* 使用 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 };
|
||||
Reference in New Issue
Block a user