密钥传输流程

  1. 系统内置一套公钥和私钥

  2. 打开前端时自动获取其中公钥(公钥不需要加密)

  3. 前端随机生成一个 sm4 的 key 和 iv,使用公钥对 key 和 iv 进行加密,传到后台 (已加密),后台使用私钥解密能拿到原始 key 和 iv,然后在生成一套公私钥,key、iv 和公钥存到 redis 中。每个用户的 key、iv 和公私钥都是不一样的,重新打开页面也会重新生成 key、iv 和公私钥私钥使用 sm4 的 cbc 模式加密后传给前端已加密

  4. 前端再使用刚才随机生成的 key 和 iv 解密拿到私钥。前端使用这个私钥对请求进行签名


前端向后端加密传输

  1. 前端拦截请求,对请求中 body 使用 sm2 的公钥加密或使用 sm4 cbc 模式加密(目前使用的 sm2 加密)

  2. 后端收到请求后,在验签之前使用私钥把 body 数据解密,然后再验签。

  3. 前端拦截请求,对请求中 body 先使用 sm3 计算哈希,然后把哈希值和明文数据拼接后,使用 sm2 公钥加密或使用 sm4 cbc 模式加密(目前使用的 sm2 加密)

  4. 后端收到请求后,在验签之前使用私钥把 body 数据解密,然后验证 sm3 哈希是否正确,最好在验签。

后端向前端加密传输

  1. 后端接口返回之前,对返回数据使用 sm2 公钥加密或使用 sm4 cbc 模式加密(目前使用 sm4 cbc)
  2. 使用 sm2 私钥对数据签名
  3. 前端后端返回数据后,先用 sm2 公钥验证签名,然后再对数据进行解密后使用。

请求签名流程

使用上一步拿到的私钥进行签名

前端

  1. 获取当前时间戳
  2. 生成随机字符串
  3. 合并请求参数、随机字符串、时间戳
  4. 对合并后的对象按照属性名称进行排序
  5. 使用 sm3 获取(方法+排序后对象)的摘要当作签名值
  6. 使用私钥对摘要和请求方法进行签名(sm2)
  7. 把时间戳、随机字符串、签名方法请求头中。

后端

  1. 判断有没有时间戳、随机字符串、签名。没有则拒绝
  2. 判断时间戳时间和当前时间,不能超过 60s
  3. 使用和前端同样的方式对参数、随机字符串、时间戳进行合并排序、计算摘要
  4. 使用公钥对请求方法和处理后的数据验签。

排序方法

参与排序的包括 body、query、params

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
function sortObject(obj) {
if (Object.prototype.toString.call(obj) === "[object Object]") {
const sortData = {}; // 排序后的对象
Object.keys(obj)
.sort()
.forEach((key) => {
if (Object.prototype.toString.call(obj[key]) === "[object Object]") {
sortData[key] = sortObject(obj[key]);
} else if (Array.isArray(obj[key])) {
sortData[key] = obj[key].map((e) => {
return sortObject(e);
});
} else if (typeof obj[key] === "number") {
sortData[key] = obj[key].toString();
} else if (typeof obj[key] === "string") {
sortData[key] = obj[key];
} else if (typeof obj[key] === "boolean") {
sortData[key] = obj[key].toString();
} else {
if (obj[key] === null || obj[key] === undefined) {
// 忽略
} else {
sortData[key] = obj[key];
}
}
});
return sortData;
}
if (typeof obj === "boolean") {
return obj.toString();
}
return obj;
}

CosfES
9nCg7C