App端安全性加密策略

为了保障政府主体的数据通信安全,涉及到敏感数据在公网上的传输时,需要对请求入参和响应进行非对称加密,且保证以下两点以确认安全性:

  1. 客户端不留存私钥(存在反编译和暴力解包风险)
  2. **私钥在任何时候都不经过响应体进行返回(**无私钥无法解密,私钥只能明文传输,不安全)

鉴于此,结合互联网上流行的加解密策略,我们针对请求响应两个阶段进行了不同的加解密策略设计。请求加密后,是由服务端验证 + 解密,是相对安全的。但是反过来,响应报文需要由客户端解密,客户端是不安全的,app和web都可以被反编译,密钥即使加密缓存也可能被破解。

需要讨论的点是:

  1. 使用高位数的非对称加密算法已经足够安全,黑客除非得到私钥,否则不可能篡改请求(防篡改和加密二选一?结合使用?)
  2. 防篡改机制在一定程度上存在安全隐患,比较适用于后端与后端对接。按照签名key的特殊性,应该只留存于服务端。目前三晋先锋使用场景主要在开放平台,后端对接,没什么问题,但是放在app上就存在一定隐患。已经使用改进方案,基于用户登录请求分发签名key,加密缓存在Jwt Token中,后端校验token的同时校验签名key匹配。

下面的方案主要基于结合使用的方案,安全系数非常高,但是在高并发场景下,对性能有一定损耗。

无论是从性能还是从安全性角度上,实际使用中我们用国家非对称安全加密算法(SM2,简称国密2算法),对RSA进行了完全替代。

一、签名算法

签名算法是一种防止篡改的机制,但是本质上并不安全。黑客得知了签名算法,并得到约定的key时(很容易在客户端反编译取得),就会变得畅通无阻。不过,签名算法 + 非对称加密算法,让黑客从根本上无法得到真正的请求体。本小节默认所有的请求体和响应体都是加密状态,加密算法下小节提。使用更安全的方案,参考序章

目前app服务端的验签算法已经完全能满足要求,这里结合沃支付接口和党建接口,对于请求和响应都进行验签,以保证安全性。进行整合后设计如下:

2.1.1 签名报文的解析验证流程

服务端:

#mermaid-svg-eBQyTizTw8taQPrs {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-eBQyTizTw8taQPrs .error-icon{fill:#552222;}#mermaid-svg-eBQyTizTw8taQPrs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eBQyTizTw8taQPrs .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-eBQyTizTw8taQPrs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eBQyTizTw8taQPrs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eBQyTizTw8taQPrs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eBQyTizTw8taQPrs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eBQyTizTw8taQPrs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eBQyTizTw8taQPrs .marker.cross{stroke:#333333;}#mermaid-svg-eBQyTizTw8taQPrs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eBQyTizTw8taQPrs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eBQyTizTw8taQPrs .cluster-label text{fill:#333;}#mermaid-svg-eBQyTizTw8taQPrs .cluster-label span{color:#333;}#mermaid-svg-eBQyTizTw8taQPrs .label text,#mermaid-svg-eBQyTizTw8taQPrs span{fill:#333;color:#333;}#mermaid-svg-eBQyTizTw8taQPrs .node rect,#mermaid-svg-eBQyTizTw8taQPrs .node circle,#mermaid-svg-eBQyTizTw8taQPrs .node ellipse,#mermaid-svg-eBQyTizTw8taQPrs .node polygon,#mermaid-svg-eBQyTizTw8taQPrs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eBQyTizTw8taQPrs .node .label{text-align:center;}#mermaid-svg-eBQyTizTw8taQPrs .node.clickable{cursor:pointer;}#mermaid-svg-eBQyTizTw8taQPrs .arrowheadPath{fill:#333333;}#mermaid-svg-eBQyTizTw8taQPrs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eBQyTizTw8taQPrs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eBQyTizTw8taQPrs .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-eBQyTizTw8taQPrs .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-eBQyTizTw8taQPrs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eBQyTizTw8taQPrs .cluster text{fill:#333;}#mermaid-svg-eBQyTizTw8taQPrs .cluster span{color:#333;}#mermaid-svg-eBQyTizTw8taQPrs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eBQyTizTw8taQPrs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
Y
N
开始
从请求头取得Sign
基于请求体计算sign
比较sign相等
验证成功
验证失败
结束

客户端:

#mermaid-svg-BhrPHn56zAvgVEAH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-BhrPHn56zAvgVEAH .error-icon{fill:#552222;}#mermaid-svg-BhrPHn56zAvgVEAH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BhrPHn56zAvgVEAH .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-BhrPHn56zAvgVEAH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BhrPHn56zAvgVEAH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BhrPHn56zAvgVEAH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BhrPHn56zAvgVEAH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BhrPHn56zAvgVEAH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BhrPHn56zAvgVEAH .marker.cross{stroke:#333333;}#mermaid-svg-BhrPHn56zAvgVEAH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BhrPHn56zAvgVEAH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BhrPHn56zAvgVEAH .cluster-label text{fill:#333;}#mermaid-svg-BhrPHn56zAvgVEAH .cluster-label span{color:#333;}#mermaid-svg-BhrPHn56zAvgVEAH .label text,#mermaid-svg-BhrPHn56zAvgVEAH span{fill:#333;color:#333;}#mermaid-svg-BhrPHn56zAvgVEAH .node rect,#mermaid-svg-BhrPHn56zAvgVEAH .node circle,#mermaid-svg-BhrPHn56zAvgVEAH .node ellipse,#mermaid-svg-BhrPHn56zAvgVEAH .node polygon,#mermaid-svg-BhrPHn56zAvgVEAH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BhrPHn56zAvgVEAH .node .label{text-align:center;}#mermaid-svg-BhrPHn56zAvgVEAH .node.clickable{cursor:pointer;}#mermaid-svg-BhrPHn56zAvgVEAH .arrowheadPath{fill:#333333;}#mermaid-svg-BhrPHn56zAvgVEAH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BhrPHn56zAvgVEAH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BhrPHn56zAvgVEAH .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-BhrPHn56zAvgVEAH .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-BhrPHn56zAvgVEAH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BhrPHn56zAvgVEAH .cluster text{fill:#333;}#mermaid-svg-BhrPHn56zAvgVEAH .cluster span{color:#333;}#mermaid-svg-BhrPHn56zAvgVEAH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BhrPHn56zAvgVEAH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
Y
N
开始
从响应头取得Sign
基于响应体计算sign
比较sign相等
验证成功
验证失败,抛出异常
结束

2.1.2 签名规则

签名的生成规则在服务端和客户端需要保持一致。额外的,为了通信的对称性,还约定了一个key额外的拼接到请求体后面,再进行md5编码运算,最终比对md5值是否匹配。

我们约定了重要的请求必须以POST请求发送,所以将整个请求体按照字符串的形式进行处理,拼接约定的签名key作为额外的内容,然后进行md5-hex32加密字符串。

例如:

用户请求POST http://api.ht-travel.gov.cn/api/scenes,约定的key6fg3c7e5b2z0f03h1, 请求体为:

{"name": "大槐树","page": 0,"size": 10,"order": "asc"}

则将上述内容作为整体字符串,直接拼接key,得到

{"name": "大槐树","page": 0,"size": 10,"order": "asc"}6fg3c7e5b2z0f03h1

然后使用HEX32的格式对上面的字符串进行md5编码,结果为3e40a7c47fd31e80ed8a8074bff5087b,该值就是签名内容。

最终,用户访问这个接口的正确内容为:

{
  "headers": {
    "Authorization": "用户token",
    "Signature": "3e40a7c47fd31e80ed8a8074bff5087b",
    "Channel": "ios",
  },
  "body": "{"name": "大槐树","page": 0,"size": 10,"order": "asc"}",
}

响应的签名算法也一致。需要客户端进行验签确认。

签名key在客户端和服务端都必须留存,且内容一致。

2.1.3 签名key的生成与验证

签名key由于其客户端留存性,对于安全性而言是一个隐患。在安全性的保证方式面前,我们优先选择用户绑定的方式,这种方式安全、明确、且较为容易实现。

分发时机:用户登录,返回token的同时返回签名key客户端缓存key和token。**注意,返回的token和key的报文都是经过加密的,无法破译。**在token有效期内,签名key一直有效,如果用户token被撤回或过期,签名key失效,无法验证通过,最大程度保证了安全性和可逆性。

**验证时机:**用户请求接口,携带token以及使用签名key进行签名后,服务端从token信息中解析出key,使用签名算法进行计算,比对。安全性在于:

  1. token永远都是由服务器发放,客户端永远无法伪造,因为不知道密钥。
  2. 签名key和token严格绑定,如果伪造签名key,绝对无法通过验证
  3. 签名key无法被拦截,不会明文交互于互联网,仅存在于用户手机缓存,且每个用户都不一样。token经过加密,即使截获token,也无法成功发送任何请求。

综上所述,该方案的安全性基本上可以达到90%以上(排除暴力破解和客户端设备被偷盗)

二、请求报文加密(RSA-256 SM2 国密2算法)

请求报文加密的基础条件是:

  1. 仅允许使用POST请求发送参数或请求数据,以保证参数的key和value都不被泄露
  2. 客户端需要通过签名算法,保证请求体不被篡改,然后再使用公钥加密。(公钥是公开透明,缓存在客户端,所以必须加签名算法防止恶意篡改伪造)
  3. 加密的目的和签名算法不同,主要是为了:保证传输过程中内容的保密性,让参数和值均不透明

2.1 加密算法

这两种和咱们现在登录接口的加密方式都基本一致,请求报文加解密基本上就可以定这种了,现在主要是响应报文加解密的安全性需要讨论。党建那个也没涉及到响应报文的解密场景。

2.2 加解密工作模式

由服务端生成唯一的一对SM2-ECC密钥对(椭圆曲线算法),公钥提供给客户端留存一份,私钥在服务端留存。

客户端发送请求前,统一使用SM2算法,用公钥对请求体进行加密,然后才发送。服务端接收后,先对报文用私钥解密后,再进行签名验证或者后续操作。

#mermaid-svg-8rAjCvtXBFAirXIe {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-8rAjCvtXBFAirXIe .error-icon{fill:#552222;}#mermaid-svg-8rAjCvtXBFAirXIe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8rAjCvtXBFAirXIe .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-8rAjCvtXBFAirXIe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8rAjCvtXBFAirXIe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8rAjCvtXBFAirXIe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8rAjCvtXBFAirXIe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8rAjCvtXBFAirXIe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8rAjCvtXBFAirXIe .marker.cross{stroke:#333333;}#mermaid-svg-8rAjCvtXBFAirXIe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8rAjCvtXBFAirXIe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8rAjCvtXBFAirXIe .cluster-label text{fill:#333;}#mermaid-svg-8rAjCvtXBFAirXIe .cluster-label span{color:#333;}#mermaid-svg-8rAjCvtXBFAirXIe .label text,#mermaid-svg-8rAjCvtXBFAirXIe span{fill:#333;color:#333;}#mermaid-svg-8rAjCvtXBFAirXIe .node rect,#mermaid-svg-8rAjCvtXBFAirXIe .node circle,#mermaid-svg-8rAjCvtXBFAirXIe .node ellipse,#mermaid-svg-8rAjCvtXBFAirXIe .node polygon,#mermaid-svg-8rAjCvtXBFAirXIe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8rAjCvtXBFAirXIe .node .label{text-align:center;}#mermaid-svg-8rAjCvtXBFAirXIe .node.clickable{cursor:pointer;}#mermaid-svg-8rAjCvtXBFAirXIe .arrowheadPath{fill:#333333;}#mermaid-svg-8rAjCvtXBFAirXIe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8rAjCvtXBFAirXIe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8rAjCvtXBFAirXIe .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-8rAjCvtXBFAirXIe .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-8rAjCvtXBFAirXIe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8rAjCvtXBFAirXIe .cluster text{fill:#333;}#mermaid-svg-8rAjCvtXBFAirXIe .cluster span{color:#333;}#mermaid-svg-8rAjCvtXBFAirXIe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8rAjCvtXBFAirXIe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
发送到服务端
开始
准备请求
使用公钥加密请求体
服务端使用私钥解密
得到明文报文
结束

三、响应报文加密(KEY-MAP + clientKey + SM2-ECC)

上述请求报文加密的方案非常常见,且着实有效,在市场和生产环境中已经得到了充分的认证。与请求报文不同的是,响应报文必须在客户端实现解密,才可保障在传输过程中全程密文的安全性。三晋先锋的响应报文加解密仍然使用RSA,且客户端和服务端使用同一套私钥,虽然每个对接方都有独立的密钥对, 但是仍然有着较大的安全隐患。不过由于三晋先锋是面向服务端对接(不直接请求自客户端,我们是),所以这部分风险相当小。当然这些不在我们讨论的范畴内,我们的目的是为了保障传输过程中的安全性。

安全性设计不存在绝对的安全。但是我们会尽可能的加大其安全系数。为此,我们采用端到端加密的方案,深度保障:

  1. 客户端不留存密钥,密钥和硬件+用户关联,实现端到端的通信。每台手机的密钥都不一样,除非手机被偷,否则是安全的。具体的,我们要做到:

(1)同一个设备,不同的用户,有着不同的密钥

(2)不同设备,同一个用户,有着不同的密钥

(3)不用设备,不同用户,有着不同的密钥

(4)同一设备,同一用户,退出登录后再登录,有着不同的密钥

  1. 密钥基于注册的方式进行单次通信,在生命周期内仅存在一对一的映射,再次注册之后之前的密钥会失效。注册时机埋点在用户成功后,客户端的回调处理里,类似于微信,钉钉异地登录顶掉目前登录的设备一样。

3.1 注册密钥 + 服务端加密策略

为了尽可能的规避客户端和服务端之间发生密钥交换的情况,我们设计了一组密钥生成策略。以RSA密钥生成原理而言,密钥的生成需要一组随机值,随机值的获取,一种是UUID,基于硬件生成的唯一ID,一种就是机器UDID(设备唯一识别码)。方案甄选时,由于UDID在iOS设备上不再受到支持,故放弃。

最终的实现方案是:**用户登录换取token成功后,客户端基于RSA256算法,基于一个UUID生成一套密钥对。然后通过签名和加密算法,将公钥注册到服务端。**密钥对一个用户同时只有一对可用。

使用国密2椭圆曲线算法后,不再需要随机值,方案便简化为:

用户登录换取token成功后,客户端基于js版本的国密2算法库,当下直接生成一套密钥对,然后通过签名和加密算法,将公钥注册到服务器,并与当前token进行归属绑定。token有效期间,仅允许存在一套绑定。

3.2 注册设备逻辑

根据我们上面的设计,注册设备的时机是用户登录,和token进行绑定,这里我们描述一下登录认证接口(注册接口同理,理论上,注册即登录,最终也会对key进行绑定)

POST http:// s e r v e r : {server}: server:{port}/api/users/authenticate

请求头:

{
  "Sign": "用户签名" 
}

请求报文如下(加密的):

{
  "username": "",
  "password": "",
  ...,
  "publicKey": "ALSDFJLASJDFASLJKFSADF7SDF78AS876FSA98DF9SA8F7S89ADF798==="
}

不返回任何响应。

该接口要求登录。原则上,将用户ID作为最小单元进行映射。

流程图如下:

#mermaid-svg-i1SlhD73TBB28ket {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-i1SlhD73TBB28ket .error-icon{fill:#552222;}#mermaid-svg-i1SlhD73TBB28ket .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-i1SlhD73TBB28ket .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-i1SlhD73TBB28ket .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-i1SlhD73TBB28ket .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-i1SlhD73TBB28ket .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-i1SlhD73TBB28ket .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-i1SlhD73TBB28ket .marker{fill:#333333;stroke:#333333;}#mermaid-svg-i1SlhD73TBB28ket .marker.cross{stroke:#333333;}#mermaid-svg-i1SlhD73TBB28ket svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-i1SlhD73TBB28ket .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-i1SlhD73TBB28ket .cluster-label text{fill:#333;}#mermaid-svg-i1SlhD73TBB28ket .cluster-label span{color:#333;}#mermaid-svg-i1SlhD73TBB28ket .label text,#mermaid-svg-i1SlhD73TBB28ket span{fill:#333;color:#333;}#mermaid-svg-i1SlhD73TBB28ket .node rect,#mermaid-svg-i1SlhD73TBB28ket .node circle,#mermaid-svg-i1SlhD73TBB28ket .node ellipse,#mermaid-svg-i1SlhD73TBB28ket .node polygon,#mermaid-svg-i1SlhD73TBB28ket .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-i1SlhD73TBB28ket .node .label{text-align:center;}#mermaid-svg-i1SlhD73TBB28ket .node.clickable{cursor:pointer;}#mermaid-svg-i1SlhD73TBB28ket .arrowheadPath{fill:#333333;}#mermaid-svg-i1SlhD73TBB28ket .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-i1SlhD73TBB28ket .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-i1SlhD73TBB28ket .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-i1SlhD73TBB28ket .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-i1SlhD73TBB28ket .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-i1SlhD73TBB28ket .cluster text{fill:#333;}#mermaid-svg-i1SlhD73TBB28ket .cluster span{color:#333;}#mermaid-svg-i1SlhD73TBB28ket div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-i1SlhD73TBB28ket :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
开始
用户登录
生成UUID
生成RSA密钥对本地缓存
公钥签名和加密
发送注册请求
结束

3.3 服务端加密和密钥缓存

服务端接收到绑定请求后,会将该密钥对进行缓存,放入redis用户缓存区,以便后续使用。放入redis,也能保障分布式环境下的密钥原子性问题,保证一个用户同一时刻只有一对密钥可用。

流程图如下:

#mermaid-svg-6dnw0qh2YPaYptM5 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6dnw0qh2YPaYptM5 .error-icon{fill:#552222;}#mermaid-svg-6dnw0qh2YPaYptM5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6dnw0qh2YPaYptM5 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-6dnw0qh2YPaYptM5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6dnw0qh2YPaYptM5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6dnw0qh2YPaYptM5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6dnw0qh2YPaYptM5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6dnw0qh2YPaYptM5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6dnw0qh2YPaYptM5 .marker.cross{stroke:#333333;}#mermaid-svg-6dnw0qh2YPaYptM5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6dnw0qh2YPaYptM5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6dnw0qh2YPaYptM5 .cluster-label text{fill:#333;}#mermaid-svg-6dnw0qh2YPaYptM5 .cluster-label span{color:#333;}#mermaid-svg-6dnw0qh2YPaYptM5 .label text,#mermaid-svg-6dnw0qh2YPaYptM5 span{fill:#333;color:#333;}#mermaid-svg-6dnw0qh2YPaYptM5 .node rect,#mermaid-svg-6dnw0qh2YPaYptM5 .node circle,#mermaid-svg-6dnw0qh2YPaYptM5 .node ellipse,#mermaid-svg-6dnw0qh2YPaYptM5 .node polygon,#mermaid-svg-6dnw0qh2YPaYptM5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6dnw0qh2YPaYptM5 .node .label{text-align:center;}#mermaid-svg-6dnw0qh2YPaYptM5 .node.clickable{cursor:pointer;}#mermaid-svg-6dnw0qh2YPaYptM5 .arrowheadPath{fill:#333333;}#mermaid-svg-6dnw0qh2YPaYptM5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6dnw0qh2YPaYptM5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6dnw0qh2YPaYptM5 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-6dnw0qh2YPaYptM5 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-6dnw0qh2YPaYptM5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6dnw0qh2YPaYptM5 .cluster text{fill:#333;}#mermaid-svg-6dnw0qh2YPaYptM5 .cluster span{color:#333;}#mermaid-svg-6dnw0qh2YPaYptM5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6dnw0qh2YPaYptM5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
开始
用户请求
解密请求
处理逻辑
生成响应体
用户身份确认公钥
使用公钥加密
发送响应
结束

3.4 客户端解密请求体

注册密钥后,客户端已经可以正常发送请求。服务端返回的报文为密文,客户端使用当前缓存的密钥对,得到私钥进行解密。解密只有成功和失败两种结果,解密成功,正常处理后续逻辑;解密失败,客户端触发退出登录动作,弹出用户的非法请求。

3.5 安全性加固和时效策略考虑

本方案虽然能够严格保障通信安全,但是存在操作漏洞(不存在传输过程漏洞)。如果黑客已经取得了用户token,并且截获了我们的公钥,得到了签名算法,就可以自由的生成UUID强行换取密钥对进行恶意注册,显然这是不被允许的。

为了规避这一点,我们要求设备在用户登录时就提供公钥信息,这样一来,黑客不光得截获token,还得熟知用户的账号,密码或验证码等信息,而且没有请求公钥无法解密,最终黑客将束手无策。

我们将上述方案改造为和登录接口结合的策略。用户输入账号密码(验证码)后,由app端执行密钥生成,并拼接到登录请求体内。

请求报文将变为:

{
  "phone": "152123121231",
  "code": "215331",
  "key": "ALSDFJLASJDFASLJKFSADF7SDF78AS876FSA98DF9SA8F7S89ADF798==="
}

响应仅返回token

四、 全局渠道标识

app在接口调用过程中,需要明确标记调用渠道,以方便之后的埋点、终端统计等功能。

需要全局的在请求头中添加Channel,可选值为ios, android, web, h5