关于前端加密的实现
VoidWh 2022-01-24
Axios
encrypt
加密
# 背景
前端vue3 后端 express + web socket + postgreSQL
实时交互的游戏,涉及控制台,显示器,客户端交互
# 需要解决的问题
- 由于用户标识为各项参数的组合,需要防止被分析出标识规则
- 前后端数据为明文传输,用户可通过开发者工具查看数据
# 思路
- 路由(参数)加密
- 请求(参数)加密,后端接受请求后解密
- 前端缓存数据加密
# 实现
加密方案
使用 crypto-js 进行数据的字符串的加密
// encryption.js
const CryptoJS = require('crypto-js')
// 将此处改为你的加密code
const baseCryptoCode = CryptoJS.enc.Latin1.stringify('SecretCode')
const getKeyHex = (cryptoCode) =>
CryptoJS.enc.Latin1.parse(cryptoCode || baseCryptoCode)
const getIvHex = () => CryptoJS.enc.Latin1.parse(baseCryptoCode)
/**
* 解密
* @param data
* @returns {string}
*/
function getDecrypt(data) {
let keyHex = getKeyHex()
let ivHex = getIvHex()
let decrypted = CryptoJS.AES.decrypt(
{
ciphertext: CryptoJS.enc.Base64.parse(data),
},
keyHex,
{
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding,
iv: ivHex,
}
).toString(CryptoJS.enc.Utf8)
try {
decrypted = JSON.parse(decrypted)
} catch (e) {
console.warn(e)
}
return decrypted
}
/**
* 加密
* @param {String} key
* @param {String} cryptoCode
* @returns {string}
*/
function getEncrypt(key, cryptoCode) {
let keyHex = getKeyHex(cryptoCode)
let ivHex = getIvHex()
try {
key = JSON.stringify(key)
} catch (e) {
console.warn(e)
}
return CryptoJS.AES.encrypt(key, keyHex, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding,
iv: ivHex,
}).toString()
}
module.exports = {
getEncryptToBase64: (key, cryptoCode) => {
let encryptStr = getEncrypt(key, cryptoCode)
let wordArray = CryptoJS.enc.Utf8.parse(encryptStr)
return CryptoJS.enc.Base64.stringify(wordArray)
},
/**
* 对base64数据解密 先解析base64,在做解密
* @param {String} data
* @returns {string}
*/
getDecryptByBase64: (data) => {
let parsedWordArray = CryptoJS.enc.Base64.parse(data)
let decryptStr = parsedWordArray.toString(CryptoJS.enc.Utf8)
return getDecrypt(decryptStr)
},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
以上为加密的核心文件
# 路由加密解密
将其中的两个方法传入路由的解析钩子函数
// query.js
// 用作Vue前端的路由加密和解密的函数
const {
getEncryptToBase64: encrypt,
getDecryptByBase64: decrypt,
} = require('./encryption')
const encodeReserveRE = /[!'()*]/g
const encodeReserveReplacer = (c) => '%' + c.charCodeAt(0).toString(16)
const commaRE = /%2C/g
const encode = (str) =>
encodeURIComponent(str)
.replace(encodeReserveRE, encodeReserveReplacer)
.replace(commaRE, ',')
const decode = decodeURIComponent
module.exports = {
/**
* 序列化对象 并加密
* @param {Object} obj
*/
stringifyQuery: (obj) => {
// 序列化方法使用的vue-router中的原方法,详见vue-router/src/query.js
// 唯一的修改时最后返回的时候,对参数进行了加密
const res = obj
? Object.keys(obj)
.map((key) => {
const val = obj[key]
if (val === undefined) {
return ''
}
if (val === null) {
return encode(key)
}
if (Array.isArray(val)) {
const result = []
val.forEach((val2) => {
if (val2 === undefined) {
return
}
if (val2 === null) {
result.push(encode(key))
} else {
result.push(encode(key) + '=' + encode(val2))
}
})
return result.join('&')
}
return encode(key) + '=' + encode(val)
})
.filter((x) => x.length > 0)
.join('&')
: null
return res ? `?${encrypt(res)}` : ''
},
/**
* 解密 解析 字符串参数
* @param {String}} query
*/
parseQuery: (query) => {
// 先对query进行解密
// 在使用vue-router中的解析方法对query进行解析,详见vue-router/src/query.js
const res = {}
query = query.trim().replace(/^(\?|#|&)/, '')
if (!query) {
return res
}
// 解密
try {
query = decrypt(query)
} catch (e) {
// FIXME 解密失败时,不做处理,返回原参数供前端解析
}
query.split('&').forEach((param) => {
const parts = param.replace(/\+/g, ' ').split('=')
const key = decode(parts.shift())
const val = parts.length > 0 ? decode(parts.join('=')) : null
if (res[key] === undefined) {
res[key] = val
} else if (Array.isArray(res[key])) {
res[key].push(val)
} else {
res[key] = [res[key], val]
}
})
return res
},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// router.js
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
stringifyQuery: stringifyQuery,
parseQuery: parseQuery,
routes,
})
export default router
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# axios 的请求参数加密
需要注意:GET 和 POST/PUT/DELETE 的请求传参方式不同,分别为 params 和 data
// request interceptor
request.interceptors.request.use(
(config) => {
const encryptData = {}
if (config.method === 'get') {
encryptData[defaultSettings.BASE_ENCRYPT_KEY] = encrypt(config.params)
config.params = encryptData
} else {
encryptData[defaultSettings.BASE_ENCRYPT_KEY] = encrypt(config.data)
config.data = encryptData
}
return config
},
(error) => {
// do something with request error
return Promise.reject(error)
}
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# express 的请求参数解密
需要注意:GET 和 POST/PUT/DELETE 的请求传参方式不同,在 express 后端接受分别为 query 和 body
// express 的请求中间件
function middleware(req, res, next) {
logger.info(`${req.method} ${req.url}`)
if (req.method === 'GET') {
req.query =
req.query && req.query[conf.BASE_ENCRYPT_KEY]
? decrypt(req.query[conf.BASE_ENCRYPT_KEY])
: {}
logger.info(req.query)
} else {
req.body =
req.body && req.body[conf.BASE_ENCRYPT_KEY]
? decrypt(req.body[conf.BASE_ENCRYPT_KEY])
: {}
logger.info(req.body)
}
return next()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 前端缓存数据加密
主要为 localStorage 和 SessionStorage 加密, 分别加密key 和 value
import {
getEncryptToBase64 as encrypt,
getDecryptByBase64 as decrypt,
} from '@/utils/encryption'
const key = 'matrix'
const selected_key = 'selected'
const generateKey = ({ ....keys }) =>
encrypt(`${JSON.stringify(...key)}`)
class Repository {
save(data) {
localStorage.setItem(
generateKey({ ...data, key }),
encrypt(JSON.stringify(data))
)
}
load(data) {
const encryptStr = localStorage.getItem(generateKey({ ...data, key }))
return encryptStr ? JSON.parse(decrypt(encryptStr)) : null
}
clear(data) {
localStorage.removeItem(generateKey({ ...data, key }))
}
updateSelectedNumbers(data) {
sessionStorage.setItem(
generateKey({ ...data, ...{ key: selected_key } }),
encrypt(JSON.stringify(data.numbers))
)
}
loadSelectedNumbers(data) {
const encryptStr = sessionStorage.getItem(
generateKey({ ...data, ...{ key: selected_key } })
)
return encryptStr ? JSON.parse(decrypt(encryptStr)) : null
}
clearSelectedNumbers(data) {
sessionStorage.removeItem(
generateKey({ ...data, ...{ key: selected_key } })
)
}
}
export const repository = new Repository()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44