import CryptoJS from 'crypto-js'; import JSEncrypt from 'jsencrypt'; /** * 加密工具类 * 与 Vue2 基座 EncryptUtil.js 保持完全一致的实现 * 只保留 V4 相关逻辑 */ const EncryptUtil = { /** * RSA 加密 * @param word 明文字符串 */ RSAEncrypt(word: string): string { const encrypt = new JSEncrypt(); encrypt.setPublicKey( 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqPvovSfXcwBbW8cKMCgwqNpsYuzF8RPAPFb7LGsnVo44JhM/xxzDyzoYtdfNmtbIuKVi9PzIsyp6rg+09gbuI6UGwBZ5DWBDBMqv5MPdOF5dCQkB2Bbr5yPfURPENypUz+pBFBg41d+BC+rwRiXELwKy7Y9caD/MtJyHydj8OUwIDAQAB', ); return encrypt.encrypt(word) as string; }, /** * RSA 私钥解密,返回明文字符串(不做 JSON 解析) * 用于解密登录响应中的 session 字段,获取 v4-session-key * @param encryptedData Base64 密文 * @param privateKey PKCS#8 格式私钥字符串 * @returns 解密后的明文,失败时返回 null */ RSADecryptRaw(encryptedData: string, privateKey: string): string | null { try { const encrypt = new JSEncrypt(); encrypt.setPrivateKey(privateKey); const decrypted = encrypt.decrypt(encryptedData); if (!decrypted) return null; return decrypted; } catch { return null; } }, /** * AES CBC 模式加密 * - 每次加密生成随机 IV * - key 为 Hex 格式(即服务端下发的 v4-session-key) * - 返回格式:Base64(iv):Base64(密文) * @param data 待加密数据(对象或字符串) * @param hexKey 十六进制格式的会话密钥(v4-session-key) */ AESEncryptCBC(data: object | string, hexKey: string): string { // 生成随机 IV(每次加密不同) const iv = CryptoJS.lib.WordArray.random(16); const encrypted = CryptoJS.AES.encrypt( JSON.stringify(data), CryptoJS.enc.Hex.parse(hexKey), { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }, ); // IV(base64) + ':' + 密文(base64) const ivBase64 = iv.toString(CryptoJS.enc.Base64); const encryptedBase64 = encrypted.toString(); return `${ivBase64}:${encryptedBase64}`; }, /** * AES CBC 模式解密 * @param combinedText 格式:Base64(iv):Base64(密文) * @param hexKey 十六进制格式的会话密钥 * @returns 解密后的原始字符串 */ AESDecryptCBC(combinedText: string, hexKey: string): string { const parts = combinedText.split(':'); if (parts.length !== 2) { throw new Error('Invalid encrypted data format'); } const iv = CryptoJS.enc.Base64.parse(parts[0]); const key = CryptoJS.enc.Hex.parse(hexKey); const decrypted = CryptoJS.AES.decrypt(parts[1], key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, }); return decrypted.toString(CryptoJS.enc.Utf8); }, /** * 安全的 AES CBC 解密(失败时返回 null,不抛异常) * 用于响应自动解密场景 * @param combinedText 格式:Base64(iv):Base64(密文) * @param hexKey 十六进制格式的会话密钥 */ safeAESDecryptCBC(combinedText: string, hexKey: string): string | null { try { return this.AESDecryptCBC(combinedText, hexKey); } catch { return null; } }, /** * 智能响应解密 * 自动检测响应数据格式并尝试解密,失败时返回原数据 * 对应 Vue2 基座 request.js 中响应拦截器的解密逻辑 * @param responseData 响应数据(字符串或对象) * @param sessionKey 十六进制格式的会话密钥(v4-session-key) */ decryptResponse(responseData: any, sessionKey: string): any { try { let dataStr = ''; if (typeof responseData === 'string') { dataStr = responseData; } else if (typeof responseData === 'object') { dataStr = JSON.stringify(responseData); } else { dataStr = String(responseData); } const decryptedStr = this.safeAESDecryptCBC(dataStr, sessionKey); if (decryptedStr) { try { return JSON.parse(decryptedStr); } catch { return decryptedStr; } } } catch (e) { console.warn('响应解密处理失败:', e); } // 解密失败时返回原数据 return responseData; }, }; export default EncryptUtil;