瀏覽代碼

通用接口参数AES加密增加动态派生密钥逻辑,解密工具适配调整

cfort 11 月之前
父節點
當前提交
3a6bb4b6ab

+ 2 - 1
src/business/platform/form/utils/custom/joinCURD.js

@@ -3,6 +3,7 @@ import { normal } from './requestType'
 import { encryptByAes } from '@/utils/encrypt'
 import { mapValues } from 'lodash'
 import { SHOW_PLAINTEXT } from '@/constant'
+import { getToken } from '@/utils/auth'
 // 请求方式默认POST
 const post = (type, data, method = 'post', loading = false) => {
     const requestUrl = `business/v3/sys/universal/${normal[type]}`
@@ -45,7 +46,7 @@ const dealData = (args, type) => {
     const data = typeof args === 'object' ? replaceNullWithEmpty(args) : args
     const plaintext = SHOW_PLAINTEXT ? { plaintext: data } : {}
     const res = {
-        ...encryptByAes(data),
+        ciphertext: encryptByAes(data, 'dynamic', getToken()),
         ...plaintext
     }
     return JSON.stringify(res)

+ 97 - 32
src/utils/encrypt.js

@@ -1,9 +1,34 @@
 import CryptoJS from 'crypto-js'
+const key = '49PBou+TREIOzSHj'
 const key1 = CryptoJS.enc.Utf8.parse('dmngJmmO+9GMw+tu')
-const key2 = CryptoJS.enc.Utf8.parse('49PBou+TREIOzSHj')
+const key2 = CryptoJS.enc.Utf8.parse(key)
 // 固定IV 用于密码、get请求参数加密
 const defaultIv = CryptoJS.enc.Utf8.parse('5lDsNRe&UduJ97uS')
 
+// 密钥派生参数配置
+const PBKDF2_CONFIG = {
+    // 盐值长度(bits)
+    saltSize: 128,
+    // 迭代次数
+    iterations: 10000,
+    // 密钥长度(256bit)
+    keySize: 256 / 32,
+    hashAlgorithm: CryptoJS.algo.SHA256
+}
+
+// 动态密钥派生器
+const deriveDynamicKey = (token, salt) => {
+    if (!token || typeof token !== 'string') {
+        throw new Error('Invalid user token')
+    }
+
+    return CryptoJS.PBKDF2(token, salt, {
+        keySize: PBKDF2_CONFIG.keySize,
+        iterations: PBKDF2_CONFIG.iterations,
+        hasher: PBKDF2_CONFIG.hashAlgorithm
+    })
+}
+
 // 动态IV生成器
 export const generateDynamicIV = () => {
     // 1. 获取当前时间戳(8字节,64位)
@@ -31,25 +56,58 @@ export const generateDynamicIV = () => {
     return CryptoJS.lib.WordArray.create(ivWords, 16)
 }
 
-// AES加密
-export const encryptByAes = (params, type = 'normal') => {
+const xorEncrypt = (text) => {
     let encrypted = ''
-    const key = type === 'normal' ? key1 : key2
-    const iv = type === 'normal' ? generateDynamicIV() : defaultIv
-    if (typeof params === 'string' || typeof params === 'object') {
+    for (let i = 0; i < text.length; i++) {
+        const charCode = text.charCodeAt(i) ^ key.charCodeAt(i % key.length)
+        encrypted += String.fromCharCode(charCode)
+    }
+
+    return btoa(encrypted)
+}
+
+const xorDecrypt = (encryptedBase64) => {
+    // 从Base64解码
+    const decodedStr = atob(encryptedBase64)
+    let decrypted = ''
+
+    // 异或解密
+    for (let i = 0; i < decodedStr.length; i++) {
+        const charCode = decodedStr.charCodeAt(i) ^ key.charCodeAt(i % key.length)
+        decrypted += String.fromCharCode(charCode)
+    }
+
+    return decrypted
+}
+
+// AES加密
+export const encryptByAes = (params, type = 'dynamic', token) => {
+    try {
+        const isDynamic = type === 'dynamic'
+        // 生成随机盐值
+        const salt = CryptoJS.lib.WordArray.random(PBKDF2_CONFIG.saltSize)
+        // 动态派生密钥
+        const derivedKey = isDynamic ? deriveDynamicKey(token, salt) : key2
+        // 生成动态iv
+        const iv = isDynamic ? generateDynamicIV() : defaultIv
         const data = typeof params === 'string' ? params : JSON.stringify(params)
         const srcs = CryptoJS.enc.Utf8.parse(data)
-        encrypted = CryptoJS.AES.encrypt(srcs, key, {
+
+        const encrypted = CryptoJS.AES.encrypt(srcs, derivedKey, {
             iv: iv,
             mode: CryptoJS.mode.CBC,
             padding: CryptoJS.pad.Pkcs7
         })
-    }
 
-    return type === 'normal' ? {
-        ciphertext: encrypted.ciphertext.toString(CryptoJS.enc.Base64),
-        iv: iv.toString(CryptoJS.enc.Base64)
-    } : encrypted.ciphertext.toString(CryptoJS.enc.Base64)
+        if (isDynamic) {
+            const str = encrypted.ciphertext.toString(CryptoJS.enc.Base64) + '|' + iv.toString(CryptoJS.enc.Base64) + '|' + salt.toString(CryptoJS.enc.Base64) + '|' + token
+            return xorEncrypt(str)
+        }
+        return encrypted.ciphertext.toString(CryptoJS.enc.Base64)
+    } catch (error) {
+        console.error('Encryption error:', error)
+        throw new Error('Encryption failed')
+    }
 }
 
 const isValidBase64 = (str) => {
@@ -60,32 +118,39 @@ const isValidBase64 = (str) => {
     }
 }
 
-export const decryptByAes = (encryptedText, ivBase64 = null, type = 'normal') => {
-    const key = type === 'normal' ? key1 : key2
+export const decryptByAes = (ciphertext) => {
+    try {
+        // 初始化解密参数
+        let cipherToDecrypt = ciphertext
+        let iv = defaultIv
+        let key = key2
 
-    if (!isValidBase64(ivBase64)) {
-        console.error('ivBase64不是有效的 Base64 字符串')
-        return ''
-    }
-    // 解析动态IV
-    const iv = CryptoJS.enc.Base64.parse(ivBase64)
+        // 尝试XOR解密并解析参数
+        const decryptedStr = xorDecrypt(ciphertext)
+        const parts = decryptedStr.split('|')
+        if (parts.length === 4) {
+            const [ciphertextBase64, ivBase64, saltBase64, token] = parts
 
-    const options = {
-        iv: iv,
-        mode: CryptoJS.mode.CBC,
-        padding: CryptoJS.pad.Pkcs7
-    }
+            if (token && !(isValidBase64(ivBase64) && isValidBase64(saltBase64))) {
+                console.error('解密失败: 无效的IV参数或salt参数!')
+                return ''
+            }
+            // 转换编码
+            iv = CryptoJS.enc.Base64.parse(ivBase64)
+            const salt = CryptoJS.enc.Base64.parse(saltBase64)
+            // 动态派生密钥
+            key = deriveDynamicKey(token, salt)
+            cipherToDecrypt = ciphertextBase64
+        }
 
-    try {
-        const encryptedData = CryptoJS.enc.Base64.parse(encryptedText)
-        const decryptedData = CryptoJS.AES.decrypt(
-            { ciphertext: encryptedData },
+        // 解密
+        const decrypted = CryptoJS.AES.decrypt(
+            { ciphertext: CryptoJS.enc.Base64.parse(cipherToDecrypt) },
             key,
-            options
+            { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
         )
 
-        // 返回UTF-8解码的明文(去除首尾空格)
-        return decryptedData.toString(CryptoJS.enc.Utf8).trim()
+        return decrypted.toString(CryptoJS.enc.Utf8).trim()
     } catch (error) {
         console.error('AES解密失败:', error)
         return ''

+ 29 - 29
src/views/platform/system/tools/convertByAes.vue

@@ -42,7 +42,7 @@
                         size="small"
                         icon="ibps-icon-copy"
                         @click="handleCopy"
-                    >复制明文SQL</el-button>
+                    >复制明文</el-button>
                 </div>
                 <div class="item">
                     <div class="label">密文</div>
@@ -55,7 +55,7 @@
                         />
                     </div>
                 </div>
-                <div class="item">
+                <!-- <div class="item">
                     <div class="label">ivBase64</div>
                     <div class="value">
                         <el-input
@@ -65,7 +65,7 @@
                             placeholder="请输入ivBase64"
                         />
                     </div>
-                </div>
+                </div> -->
             </div>
         </div>
     </el-dialog>
@@ -111,41 +111,41 @@ export default {
             this.ciphertext = this.$common.encryptByAes(this.plaintext)
         },
         handleDecrypt () {
-            if (!this.ciphertext) {
-                return this.$message.warning('请输入密文!')
+            if (!this.ciphertext || typeof this.ciphertext !== 'string') {
+                return this.$message.warning('无效的密文输入!')
             }
-            if (!this.ivBase64) {
-                return this.$message.warning('请输入ivBase64!')
-            }
-            const temp = this.$common.decryptByAes(this.ciphertext, this.ivBase64)
+            // if (!this.ivBase64) {
+            //     return this.$message.warning('请输入ivBase64!')
+            // }
+            const temp = this.$common.decryptByAes(this.ciphertext)
             if (this.$utils.isEmpty(temp)) {
                 return this.$message.error('密文无效,无法解密!')
             }
             this.plaintext = temp
         },
         handleCopy () {
-            if (!this.plaintext) {
-                return this.$message.warning('明文为空!')
+            const text = this.plaintext
+            if (navigator.clipboard) {
+                navigator.clipboard.writeText(text).then(() => {
+                    this.$message.success('明文已复制到剪贴板!')
+                }).catch(() => this.fallbackCopy(text))
+            } else {
+                this.fallbackCopy(text)
             }
+        },
+        fallbackCopy (text) {
+            const textarea = document.createElement('textarea')
+            textarea.value = text
+            textarea.style.position = 'fixed'
+            document.body.appendChild(textarea)
+            textarea.select()
             try {
-                const temp = JSON.parse(this.plaintext)
-                const hasSql = temp.hasOwnProperty('sql')
-                // 明文是 JSON 格式
-                navigator.clipboard.writeText(temp.sql || this.plaintext).then(() => {
-                    const msg = hasSql ? '明文SQL已复制到剪贴板!' : '明文中不含SQL,已复制所有内容到剪贴板!'
-                    this.$message.success(msg)
-                }).catch(error => {
-                    this.$message.error('复制失败!')
-                    console.log(error)
-                })
-            } catch (e) {
-                // 明文不是 JSON 格式
-                navigator.clipboard.writeText(this.plaintext).then(() => {
-                    this.$message.success('明文内容已复制到剪贴板!')
-                }).catch(error => {
-                    this.$message.error('复制失败!')
-                    console.log(error)
-                })
+                document.execCommand('copy')
+                this.$message.success('明文已复制到剪贴板!')
+            } catch (err) {
+                this.$message.warning('复制失败,请手动选择内容复制!')
+            } finally {
+                document.body.removeChild(textarea)
             }
         },
         closeDialog () {