/** * 加密库:主口令 + AES-GCM 整库加密。 * local 存 vaultMeta / encryptedVault;解锁后明文放 session,不进 local; * 派生密钥仅由 background 在内存中持有。 */ (function (root, factory) { if (typeof module === "object" && module.exports) { module.exports = factory({ getCrypto: () => globalThis.crypto, getStorage: (area) => { const c = typeof chrome !== "undefined" ? chrome : null; return c?.storage?.[area] || null; }, }); } else { const api = factory({ getCrypto: () => crypto, getStorage: (area) => chrome.storage[area], }); root.CryptoStore = api; } })(typeof globalThis !== "undefined" ? globalThis : this, function ({ getCrypto, getStorage }) { const VAULT_META_KEY = "vaultMeta"; const ENCRYPTED_VAULT_KEY = "encryptedVault"; const SESSION_ENV_KEY = "environments"; const SESSION_ACTIVE_KEY = "activeEnvId"; const PBKDF2_ITERATIONS = 200000; const VAULT_VERSION = 1; function b64encode(buf) { const bytes = new Uint8Array(buf); let s = ""; for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]); return btoa(s); } function b64decode(str) { const s = atob(str); const bytes = new Uint8Array(s.length); for (let i = 0; i < s.length; i++) bytes[i] = s.charCodeAt(i); return bytes; } function randomBytes(n) { const arr = new Uint8Array(n); getCrypto().getRandomValues(arr); return arr; } async function deriveKey(password, salt, iterations) { const enc = new TextEncoder(); const keyMaterial = await getCrypto().subtle.importKey( "raw", enc.encode(password), "PBKDF2", false, ["deriveKey"] ); return getCrypto().subtle.deriveKey( { name: "PBKDF2", salt, iterations, hash: "SHA-256" }, keyMaterial, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] ); } async function encryptJson(obj, key) { const enc = new TextEncoder(); const iv = randomBytes(12); const ciphertext = await getCrypto().subtle.encrypt( { name: "AES-GCM", iv }, key, enc.encode(JSON.stringify(obj)) ); return { iv: b64encode(iv), ciphertext: b64encode(ciphertext) }; } /** 解密为 JSON 对象,口令错误或数据损坏时抛错 */ async function decryptJson({ iv, ciphertext }, key) { const dec = new TextDecoder(); const plain = await getCrypto().subtle.decrypt( { name: "AES-GCM", iv: b64decode(iv) }, key, b64decode(ciphertext) ); return JSON.parse(dec.decode(plain)); } async function getLocal(key) { return (await getStorage("local").get(key))[key]; } async function hasVault() { const meta = await getLocal(VAULT_META_KEY); const cipher = await getLocal(ENCRYPTED_VAULT_KEY); return !!(meta && cipher); } async function isUnlocked() { const data = await getStorage("session").get(SESSION_ENV_KEY); return !!data[SESSION_ENV_KEY]; } /** 首次设置口令:生成盐、加密初始环境写入 local,并把明文写入 session,返回派生密钥 */ async function setupVault(password, environments) { if (!password) throw new Error("口令不能为空"); const salt = randomBytes(16); const key = await deriveKey(password, salt, PBKDF2_ITERATIONS); const cipher = await encryptJson(environments || [], key); const meta = { version: VAULT_VERSION, iterations: PBKDF2_ITERATIONS, salt: b64encode(salt), }; await getStorage("local").set({ [VAULT_META_KEY]: meta, [ENCRYPTED_VAULT_KEY]: cipher, }); const envs = environments || []; await getStorage("session").set({ [SESSION_ENV_KEY]: envs, [SESSION_ACTIVE_KEY]: envs[0]?.id || null, }); return key; } /** 解锁:解密 local 密文写入 session,口令错误抛错,返回明文与密钥 */ async function unlock(password) { const meta = await getLocal(VAULT_META_KEY); const cipher = await getLocal(ENCRYPTED_VAULT_KEY); if (!meta || !cipher) throw new Error("尚未设置口令"); const key = await deriveKey(password, b64decode(meta.salt), meta.iterations); let environments; try { environments = await decryptJson(cipher, key); } catch { throw new Error("口令错误"); } const active = (await getStorage("session").get(SESSION_ACTIVE_KEY))[SESSION_ACTIVE_KEY] || environments[0]?.id || null; await getStorage("session").set({ [SESSION_ENV_KEY]: environments, [SESSION_ACTIVE_KEY]: active, }); return { environments, key }; } async function lock() { await getStorage("session").remove([SESSION_ENV_KEY, SESSION_ACTIVE_KEY]); } async function getVaultMeta() { return getLocal(VAULT_META_KEY); } async function getEncryptedVault() { return getLocal(ENCRYPTED_VAULT_KEY); } /** 用给定密钥加密 environments 并写回 local */ async function persistEncrypted(environments, key) { const cipher = await encryptJson(environments, key); await getStorage("local").set({ [ENCRYPTED_VAULT_KEY]: cipher }); } return { VAULT_META_KEY, ENCRYPTED_VAULT_KEY, SESSION_ENV_KEY, SESSION_ACTIVE_KEY, PBKDF2_ITERATIONS, hasVault, isUnlocked, setupVault, unlock, lock, deriveKey, encryptJson, decryptJson, getVaultMeta, getEncryptedVault, persistEncrypted, b64encode, b64decode, randomBytes, }; });