[{"data":1,"prerenderedAt":10},["ShallowReactive",2],{"blog-前端接口加密实战：基于AES-CBC的参数_返回值加密方案":3},{"slug":4,"title":5,"description":6,"date":7,"content":8,"path":9},"前端接口加密实战：基于AES-CBC的参数_返回值加密方案","前端安全接口加密实战：基于AES-CBC的参数_返回值加密方案","安全接口加密实战：基于AES-CBC的参数_返回值加密","2024-10-4","# 前端接口加密实战：基于AES-CBC的参数/返回值加密方案\n\n> 附完整TypeScript代码+调用示例+密钥拆分逻辑，解决接口数据传输安全问题\n\n在前后端数据交互过程中，接口参数和返回值的加密是保障数据安全的重要手段。本文基于 `CryptoJS` 实现 AES-CBC 模式的加解密方案，结合 SessionId 动态生成密钥/偏移量，完成前端接口参数加密、返回值解密的全流程封装，适用于 Vue3 + Pinia 技术栈项目。实现该方案需先安装以下依赖：\n\n```bash\n# 安装CryptoJS（AES加解密核心库）\nnpm install crypto-js --save\n# 安装js-base64（Base64编码/解码）\nnpm install js-base64 --save\n```\n\n首先是密钥/偏移量处理工具（tools.ts），该文件主要实现从用户 SessionId 拆分 AES 所需的 key/iv，以及 Base64 编解码工具函数，代码全程带详细注释：\n\n```typescript\n// 引入Base64编码/解码库，用于密钥的Base64转换\nimport { Base64 } from 'js-base64';\n// 引入项目基础配置（保留原代码，可根据实际项目调整）\nimport { BaseKey } from '/@/config/base';\n// 引入本地存储工具（保留原代码，可根据实际项目调整）\nimport { Local, Session } from '/@/utils/storage';\n// 引入pinia相关工具，用于获取用户状态\nimport { storeToRefs } from 'pinia';\n// 引入用户信息仓库，用于获取sessionId\nimport { useUserInfo } from '/@/stores/userInfo';\n\n/**\n * @description 将密钥转换为Base64编码\n * @param {any} scret 原始密钥字符串\n * @return {*} Base64编码后的密钥（失败返回undefined）\n */\nexport const getCryptoKeyToBase64 = (scret: any) => {\n    try {\n        // 边界判断：密钥存在才执行转换\n        if (scret) {\n            // 使用js-base64库将原始密钥转为Base64编码\n            const base = Base64.encode(scret);\n            return base;\n        }\n    } catch (error) {\n        // 捕获转换异常，输出警告日志\n        console.warn(`密钥Base64编码失败`, error);\n    }\n};\n\n/**\n * @description 将Base64编码的密钥解密为原始字符串\n * @param {any} scret Base64编码的密钥\n * @return {*} 原始密钥字符串（失败返回undefined）\n */\nexport const getCryptoKeyParse = (scret: any) => {\n    try {\n        // 边界判断：Base64密钥存在才执行解析\n        if (scret) {\n            // 使用js-base64库将Base64密钥解析为原始字符串\n            return Base64.decode(scret);\n        }\n    } catch (error) {\n        // 捕获解析异常，输出警告日志\n        console.warn(`密钥Base64解析失败`, error);\n    }\n};\n\n/**\n * @description 从用户sessionId中拆分出AES加密所需的key和iv\n * @return {*} { key: 前16位字符串, iv: 后16位字符串 }\n */\nexport const TokenSplitHandle = () => {\n    try {\n        // 获取pinia中用户信息仓库实例\n        const stores = useUserInfo();\n        // 解构出用户信息响应式数据（storeToRefs保持响应式）\n        const { userInfos } = storeToRefs(stores);\n        // 边界判断：用户信息不存在则直接返回\n        if (!userInfos.value) return;\n        // 从用户信息中获取sessionId（用于生成key和iv）\n        const sessionId: any = userInfos.value?.sessionId;\n        // 将sessionId中的\"-\"替换为空（避免特殊字符影响加解密）\n        const sessionIdStr = sessionId.replace(/-/g, '');\n        if (sessionIdStr) {\n            // 截取sessionId前16位作为AES加密的key（AES-128要求key为16位）\n            const sessionIdStrStart = sessionIdStr.substring(0, 16);\n            // 截取sessionId后16位作为AES加密的iv（CBC模式要求iv为16位）\n            const sessionIdStrEnd = sessionIdStr.substring(sessionIdStr.length - 16, sessionIdStr.length);\n            // 返回key和iv供加密解密使用\n            return {\n                key: sessionIdStrStart,\n                iv: sessionIdStrEnd,\n            };\n        }\n        // 若sessionId处理失败，弹出提示（适配多语言可替换为i18n）\n        alert('Get key iv failed');\n    } catch (error) {\n        // 捕获异常，避免程序崩溃\n        console.error('拆分sessionId获取key/iv失败:', error);\n    }\n};\n```\n\n接下来是AES加解密核心工具类（crypto.ts），基于 AES-CBC 模式封装通用的加密/解密方法，支持字符串、对象等多种数据类型，兼容异常处理：\n\n```typescript\n// 引入crypto-js加密库，用于实现AES加密解密\nimport CryptoJS from 'crypto-js/crypto-js';\n// 引入Token拆分工具函数，用于从Token中提取加密所需的key和iv\nimport { TokenSplitHandle } from './tools';\n\n/**\n * @description Crypto加密工具类 - 基于AES-CBC模式实现数据加密和解密\n * @author ShiPengCheng\n * @export\n * @class Crypto\n */\nexport default class Crypto {\n    /**\n     * @description AES-CBC模式加密方法\n     * @static\n     * @param {*} word 需要加密的数据（支持字符串/对象，对象会自动转为JSON字符串）\n     * @return {*} 加密后的16进制字符串\n     * @memberof Crypto\n     */\n    static Encrypt(word: any) {\n        try {\n            // 从Token中拆分出加密所需的key（前16位）和iv（后16位）\n            const { iv, key } = TokenSplitHandle();\n            // 边界判断：如果key/iv获取失败，直接返回（避免加密异常）\n            if (!iv || !key) return;\n            // 检测数据类型：如果是对象，先转为JSON字符串（因为加密只能处理字符串）\n            if (typeof word == 'object') {\n                word = JSON.stringify(word);\n            }\n            // 执行AES-CBC加密\n            let encrypted = CryptoJS.AES.encrypt(\n                word, // 待加密的原始数据\n                CryptoJS.enc.Utf8.parse(key), // 将key转为Utf8编码的字节数组（AES要求key为16/24/32位）\n                {\n                    iv: CryptoJS.enc.Utf8.parse(iv), // 偏移量iv（CBC模式必须，要求16位）\n                    mode: CryptoJS.mode.CBC, // 加密模式：CBC（需配合iv使用，比ECB更安全）\n                    padding: CryptoJS.pad.Pkcs7, // 填充方式：Pkcs7（补齐数据长度至块大小的整数倍）\n                }\n            );\n            // 将加密结果转为16进制字符串返回（也可返回base64，根据后端要求调整）\n            return encrypted.ciphertext.toString(CryptoJS.enc.Hex);\n        } catch (error) {\n            // 捕获加密异常，避免程序崩溃\n            console.error('AES加密失败:', error);\n        }\n    }\n\n    /**\n     * @description AES-CBC模式解密方法\n     * @static\n     * @param {*} ciphertextBase64 加密后的16进制字符串\n     * @return {*} 解密后的原始字符串（若为对象可自行JSON.parse）\n     * @memberof Crypto\n     */\n    static Decrypt(ciphertextBase64: any) {\n        try {\n            // 从Token中拆分出解密所需的key和iv（需与加密时一致）\n            const { iv, key } = TokenSplitHandle();\n            // 边界判断：如果key/iv获取失败，直接返回\n            if (!iv || !key) return;\n            // 步骤1：将16进制密文解析为CryptoJS可处理的格式\n            const encryptedHex = CryptoJS.enc.Hex.parse(ciphertextBase64);\n            // 步骤2：将16进制转为Base64格式（CryptoJS解密要求输入为Base64）\n            const encryptedBase64 = CryptoJS.enc.Base64.stringify(encryptedHex);\n            // 执行AES-CBC解密\n            const decrypted = CryptoJS.AES.decrypt(\n                encryptedBase64, // 待解密的Base64格式密文\n                CryptoJS.enc.Utf8.parse(key), // 解密密钥（需与加密时一致）\n                {\n                    iv: CryptoJS.enc.Utf8.parse(iv), // 偏移量iv（需与加密时一致）\n                    mode: CryptoJS.mode.CBC, // 解密模式（需与加密时一致）\n                    padding: CryptoJS.pad.Pkcs7, // 填充方式（需与加密时一致）\n                }\n            ).toString(CryptoJS.enc.Utf8); // 将解密结果转为Utf8字符串\n            // 返回解密后的原始数据\n            return decrypted;\n        } catch (error) {\n            // 捕获解密异常，便于排查问题\n            console.error('AES解密失败:', error);\n        }\n    }\n}\n```\n\n下面是实战调用示例，首先是接口请求参数加密（前端→后端），在接口请求前对参数加密，将加密后的字符串传给后端：\n\n```typescript\nimport Crypto from '@/utils/crypto'; // 引入加密工具类\n// 1. 准备接口请求参数（对象/字符串都支持）\nconst requestParams = {\n    username: 'admin',\n    password: '123456',\n    timestamp: new Date().getTime()\n};\n// 2. 对参数进行AES加密\nconst encryptedParams = Crypto.Encrypt(requestParams);\nconsole.log('加密后的参数:', encryptedParams); // 输出16进制加密字符串\n// 3. 调用接口（将加密后的参数传给后端）\nconst apiRequest = async () => {\n    try {\n        const res = await fetch('/api/login', {\n            method: 'POST',\n            headers: {\n                'Content-Type': 'application/json'\n            },\n            body: JSON.stringify({\n                data: encryptedParams // 加密后的参数\n            })\n        });\n        const result = await res.json();\n        console.log('接口响应:', result);\n    } catch (error) {\n        console.error('接口调用失败:', error);\n    }\n};\n// 执行接口调用\napiRequest();\n```\n\n然后是接口返回值解密（后端→前端），对接收到的加密返回值进行解密，还原原始数据：\n\n```typescript\nimport Crypto from '@/utils/crypto'; // 引入加密工具类\n// 模拟后端返回的加密数据（实际从接口响应中获取）\nconst encryptedResponse = '7e8f9d0a1b2c3d4e5f6a7b8c9d0e1f2a'; // 示例16进制密文\n// 对返回值进行AES解密\nconst decryptedResponse = Crypto.Decrypt(encryptedResponse);\nconsole.log('解密后的原始数据:', decryptedResponse); // 输出解密后的字符串\n// 若原始数据是对象，需转为JSON对象\nif (decryptedResponse) {\n    const responseData = JSON.parse(decryptedResponse);\n    console.log('解析后的对象:', responseData);\n}\n```\n\n还有日常开发中对普通字符串的加解密操作示例：\n\n```typescript\nimport Crypto from '@/utils/crypto';\n// 加密普通字符串\nconst plainText = 'hello world';\nconst encryptedText = Crypto.Encrypt(plainText);\nconsole.log('加密后的字符串:', encryptedText);\n// 解密字符串\nconst decryptedText = Crypto.Decrypt(encryptedText);\nconsole.log('解密后的字符串:', decryptedText); // 输出：hello world\n```\n\n最后是核心说明与避坑指南和总结，加密模式选择上，采用AES-CBC模式（需配合iv使用），相比ECB模式更安全（ECB无偏移量，相同明文加密结果相同）；必须保证key/iv为16位（AES-128），若需更高安全级可使用24位（AES-192）或32位（AES-256）。数据兼容性方面，加密方法自动将对象转为JSON字符串，解密后需手动JSON.parse还原对象；加密结果默认返回16进制字符串，可根据后端要求改为Base64（修改encrypted.toString(CryptoJS.enc.Base64)）。密钥安全性上，key/iv从用户SessionId拆分生成，避免硬编码（硬编码密钥易被反编译获取）；需保证SessionId长度≥16位，否则拆分失败，建议后端生成SessionId时控制长度。异常处理方面，所有核心方法均包含try-catch，避免加解密失败导致程序崩溃；加密/解密前先判断key/iv是否存在，防止空值导致的加解密异常。本文实现的前端接口加解密方案具备以下特点：1. 基于AES-CBC模式，兼顾安全性与兼容性，适配绝大多数后端语言；2. 密钥动态生成（从SessionId拆分），避免硬编码风险；3. 工具类封装通用，支持字符串、对象等多种数据类型，可直接复用；4. 全程带异常处理，保证项目稳定性。该方案可直接应用于Vue3 + Pinia项目，也可适配React、Angular等其他前端框架，只需调整密钥获取逻辑即可。\n> （注：文档部分内容可能由 AI 生成）\n","/blog/前端接口加密实战：基于AES-CBC的参数_返回值加密方案",1772682575435]