const pemToArrayBuffer = (pem: string): ArrayBuffer => { const base64 = pem .replace(/-----BEGIN PUBLIC KEY-----/g, "") .replace(/-----END PUBLIC KEY-----/g, "") .replace(/\s/g, ""); const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let index = 0; index < binary.length; index += 1) { bytes[index] = binary.charCodeAt(index); } return bytes.buffer; }; const arrayBufferToBase64 = (buffer: ArrayBuffer): string => { const bytes = new Uint8Array(buffer); let binary = ""; bytes.forEach((byte) => { binary += String.fromCharCode(byte); }); return btoa(binary); }; const assertSecureCryptoAvailable = () => { if (!window.isSecureContext || !crypto?.subtle) { throw new Error("当前浏览器环境不支持安全登录加密,请使用 HTTPS 或 localhost 访问系统"); } }; export const encryptLoginPayload = async ( publicKeyPem: string, payload: { email: string; password: string; challenge: string } ): Promise => { assertSecureCryptoAvailable(); const publicKey = await crypto.subtle.importKey( "spki", pemToArrayBuffer(publicKeyPem), { name: "RSA-OAEP", hash: "SHA-256", }, false, ["encrypt"] ); const aesKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt"]); const iv = crypto.getRandomValues(new Uint8Array(12)); const encoded = new TextEncoder().encode(JSON.stringify(payload)); const encryptedData = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, encoded); const rawAesKey = await crypto.subtle.exportKey("raw", aesKey); const encryptedKey = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, publicKey, rawAesKey); const envelope = { encrypted_key: arrayBufferToBase64(encryptedKey), iv: arrayBufferToBase64(iv.buffer), data: arrayBufferToBase64(encryptedData), }; return btoa(JSON.stringify(envelope)); };