Преглед изворни кода

同步通用接口参数AES加密改为动态iv

cyy пре 11 месеци
родитељ
комит
6224a74da9
3 измењених фајлова са 107 додато и 55 уклоњено
  1. 80 28
      src/utils/encrypt.js
  2. 26 26
      src/utils/joinCURD.js
  3. 1 1
      src/utils/request.js

+ 80 - 28
src/utils/encrypt.js

@@ -1,45 +1,97 @@
 import CryptoJS from 'crypto-js'
 const key1 = CryptoJS.enc.Utf8.parse('dmngJmmO+9GMw+tu')
-const iv1 = CryptoJS.enc.Utf8.parse('sanXyqhk8+U7LPP4')
+// const iv1 = CryptoJS.enc.Utf8.parse('sanXyqhk8+U7LPP4')
 const key2 = CryptoJS.enc.Utf8.parse('49PBou+TREIOzSHj')
-const iv2 = CryptoJS.enc.Utf8.parse('5lDsNRe&UduJ97uS')
+// const iv2 = CryptoJS.enc.Utf8.parse('5lDsNRe&UduJ97uS')
+
+// 固定IV 用于密码、get请求参数加密
+const defaultIv = CryptoJS.enc.Utf8.parse('5lDsNRe&UduJ97uS')
+
+// 动态IV生成器
+export const generateDynamicIV = () => {
+  // 1. 获取当前时间戳(8字节,64位)
+  const timestamp = Date.now()
+  // 将时间戳转换为8字节的WordArray(大端序)
+  const timestampBytes = CryptoJS.enc.Hex.parse(
+    timestamp.toString(16).padStart(16, '0')
+  )
+
+  // 2. 生成8字节随机数
+  const randomBytes = CryptoJS.lib.WordArray.random(8)
+
+  // 3. 构造IV(16字节)
+  const ivWords = [
+    // 前4字节:随机数的高4字节
+    randomBytes.words[0],
+    // 接下来4字节:时间戳的高4字节
+    timestampBytes.words[0],
+    // 接下来4字节:随机数的低4字节
+    randomBytes.words[1],
+    // 最后4字节:时间戳的低4字节
+    timestampBytes.words[1]
+  ]
+
+  return CryptoJS.lib.WordArray.create(ivWords, 16)
+}
 
 // AES加密
-export const encryptByAes = (pwd, type = 'normal') => {
+export const encryptByAes = (params, type = 'normal') => {
   let encrypted = ''
   const key = type === 'normal' ? key1 : key2
-  const iv = type === 'normal' ? iv1 : iv2
-  if (typeof pwd === 'string') {
-    const srcs = CryptoJS.enc.Utf8.parse(pwd)
-    const options = {
-      iv: iv,
-      mode: CryptoJS.mode.CBC, // 使用CBC模式
-      padding: CryptoJS.pad.Pkcs7 // 使用PKCS7填充
-    }
-    encrypted = CryptoJS.AES.encrypt(srcs, key, options)
-  } else if (typeof pwd === 'object') {
-    const data = JSON.stringify(pwd)
+  const iv = type === 'normal' ? generateDynamicIV() : defaultIv
+  if (typeof params === 'string' || typeof params === 'object') {
+    const data = typeof params === 'string' ? params : JSON.stringify(params)
     const srcs = CryptoJS.enc.Utf8.parse(data)
-    const options = {
+    encrypted = CryptoJS.AES.encrypt(srcs, key, {
       iv: iv,
-      mode: CryptoJS.mode.CBC, // 使用CBC模式
-      padding: CryptoJS.pad.Pkcs7 // 使用PKCS7填充
-    }
-    encrypted = CryptoJS.AES.encrypt(srcs, key, options)
+      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)
+}
+
+const isValidBase64 = (str) => {
+  try {
+    return btoa(atob(str)) === str
+  } catch (e) {
+    return false
   }
-  return encrypted.ciphertext.toString(CryptoJS.enc.Base64)
 }
 
-export const decryptByAes = (encryptedText, type = 'normal') => {
+export const decryptByAes = (encryptedText, ivBase64 = null, type = 'normal') => {
   const key = type === 'normal' ? key1 : key2
-  const iv = type === 'normal' ? iv1 : iv2
+
+  if (!isValidBase64(ivBase64)) {
+    console.error('ivBase64不是有效的 Base64 字符串')
+    return ''
+  }
+  // 解析动态IV
+  const iv = CryptoJS.enc.Base64.parse(ivBase64)
+
   const options = {
     iv: iv,
-    mode: CryptoJS.mode.CBC, // 使用CBC模式
-    padding: CryptoJS.pad.Pkcs7 // 使用PKCS7填充
+    mode: CryptoJS.mode.CBC,
+    padding: CryptoJS.pad.Pkcs7
+  }
+
+  try {
+    const encryptedData = CryptoJS.enc.Base64.parse(encryptedText)
+    const decryptedData = CryptoJS.AES.decrypt(
+      { ciphertext: encryptedData },
+      key,
+      options
+    )
+
+    // 返回UTF-8解码的明文(去除首尾空格)
+    return decryptedData.toString(CryptoJS.enc.Utf8).trim()
+  } catch (error) {
+    console.error('AES解密失败:', error)
+    return ''
   }
-  const encryptedData = CryptoJS.enc.Base64.parse(encryptedText)
-  const decryptedData = CryptoJS.AES.decrypt({ ciphertext: encryptedData }, key, options)
-  const decryptedText = decryptedData.toString(CryptoJS.enc.Utf8)
-  return decryptedText.trim()
 }
+

+ 26 - 26
src/utils/joinCURD.js

@@ -1,55 +1,55 @@
-import request from "./request";
-import { normal } from "./requestType";
-import { encryptByAes } from "./encrypt";
-import { mapValues } from "lodash";
+import request from './request'
+import { normal } from './requestType'
+import { encryptByAes } from './encrypt'
+import { mapValues } from 'lodash'
 import { SHOW_PLAINTEXT } from '@/constant'
 
 // 请求方式默认POST
-const post = (type, data, method = "post", loading = false) => {
-  const requestUrl = `business/v3/sys/universal/${normal[type]}`;
+const post = (type, data, method = 'post', loading = false) => {
+  const requestUrl = `business/v3/sys/universal/${normal[type]}`
   // 非sql类型要关闭loading动画需传参loading:true
-  const isLoading = type === "sql" ? loading : !loading;
+  const isLoading = type === 'sql' ? loading : !loading
   return request({
     url: requestUrl,
     method,
     data: dealData(data, type),
     // 开启表单提交加载,查询接口除外
     isLoading
-  });
-};
+  })
+}
 
 const replaceNullWithEmpty = obj => {
-  function replaceValue (value) {
+  function replaceValue(value) {
     if (value === null) {
-      return "";
-    } else if (typeof value === "object") {
+      return ''
+    } else if (typeof value === 'object') {
       if (Array.isArray(value)) {
-        return value.map(item => replaceValue(item));
+        return value.map(item => replaceValue(item))
       } else {
-        return mapValues(value, v => replaceValue(v));
+        return mapValues(value, v => replaceValue(v))
       }
     } else {
-      return value;
+      return value
     }
   }
-  return replaceValue(obj);
-};
+  return replaceValue(obj)
+}
 
 // // 处理数据
 const dealData = (args, type) => {
   // sql方法特殊处理
-  if (type === "sql") {
+  if (type === 'sql') {
     args = {
-      sql: args.replace(/\n/g, " ")
-    };
+      sql: args.replace(/\n/g, ' ')
+    }
   }
-  const data = typeof args === "object" ? replaceNullWithEmpty(args) : args;
+  const data = typeof args === 'object' ? replaceNullWithEmpty(args) : args
   const plaintext = SHOW_PLAINTEXT ? { plaintext: data } : {}
   const res = {
-    ciphertext: encryptByAes(data),
+    ...encryptByAes(data),
     ...plaintext
-  };
-  return JSON.stringify(res);
-};
+  }
+  return JSON.stringify(res)
+}
 
-export default post;
+export default post

+ 1 - 1
src/utils/request.js

@@ -93,7 +93,7 @@ service.interceptors.request.use(async config => {
   if (config.method.toUpperCase() === 'GET') {
     if (ENCRYPT_GET_PARAMS) {
       config.params = {
-        _p: Utils.isNotEmpty(config.params) ? encryptByAes(JSON.stringify(config.params)) : undefined,
+        _p: Utils.isNotEmpty(config.params) ? encryptByAes(JSON.stringify(config.params), 'get') : undefined,
         _t: Date.parse(new Date()) / 1000
       }
     } else {