为确保传输数据安全,推荐使用安全协议,如 https,无法使用时才使用该方案(通过协商密钥算法协商会话密钥,然后通过密钥加密传输数据)。

术语定义:

  • 公钥、私钥

    • 公钥会进入网络传输的,该部分是公开的。

    • 私钥由部分不允许公开。

  • DH 密钥交换(协商)算法

    • 全称为Diffie-Hellman ,该算法仅传输双方各自的公钥即可使得双方计算出相同的key,可以使用这个key作为密钥加密信息;即使攻击者只获取到双方公钥也无法计算出Key值。

    • 关键点:需要保证在互换双方公钥时不被中间人进行替换,否则可能遭受中间人攻击。

  • 共享密钥/会话密钥

    • DH算法中,双方进行密钥(公钥)交换后,通过对方公钥和己方私钥生成的Key值(双方结果相同)。

    • 只要私钥不被破解,该值就是安全的。

  • 数据密钥

    • 真正用于加密数据的key,每次会话随机生成,使用该密钥加密敏感数据,然后用共享密钥和共享iv( shareIv )进行加密,随后该值将被销毁。

    • 发送数据时,会将 使用该值加密过的敏感数据密文 、 共享iv 、 使共享密钥加密过的数据密钥(该值) 传递给对方,对方会先解密出 数据密钥明文 ,然后解密出本次传输的敏感信息。

    • 该值仅在内存中使用时临时生成和恢复,用后即销毁,因为该值是临时的,且不在网络中传输,所以可以保证敏感数据的安全。

  • 安全会话标识

    • 类似SessionId,用于记录对方是谁,通过该值来判断是否已经进行秘钥交换、获取共享秘钥,双方都会保存该标识。

  • AES支持算法

    • 加密用的规则,请求方所支持的AES加密种类,如果支持多个,用分号分割。这些值取决于代码实现的种类。

  • AES算法选型

    • 响应方从请求方的AES-supports挑选一个随机的加密方法,作为本次会话中使用的AES加密算法。

  • B2S(bytes[])

    • 代表将bytes[] 转化为字符串,一般采用base64编码。

交互流程

  • 整个加密流程的实现,分成客户端部分和服务端部分,对于大部分的服务,均需实现(既会提供给服务调用,也会调用其他服务)。

  • 传输加密前,双方需要通过协商,来确定加密敏感信息所用的秘钥。

  • 每一次的协商,都会生成一个唯一的 X-Sid ,用于双方后续的通信。当服务端发现 X-Sid 过期时,需要返回约定好的错误码,让客户端重新发起协商和请求。

客户端-发起协商请求

  1. 生成 ECDH 的公私钥对(公钥 ClientPublicKey 和私钥 ClientPrivateKey)

  2. 生成一个唯一的会话ID(X-Sid)

  3. 生成客户端支持的加/解密规则(aesSupports)

  4. 生成请求接口用的认证 Token

    • Token的签名为:<X-Sid>B2S(<ClientPublicKey>)

    • 如 X-Sid='ABC',B2S(<ClientPublicKey>)='XYZ',则最终为 ABCXYZ

  5. 将Token放入header中,将S(PublicKeyA)也即转换为HEX的公钥、X-Sid、AES-supports字段放入 Body 中,并调用服务端的协商接口。

  6. 可选步骤:填充 negotiateVersion 为 1,代表使用第一个版本的协商算法(ECDH),该字段只是为了后续版本可能更新,只有一套方案时可以直接采用默认值,节省网络开销。

Table 1. 发起协商请求所需参数
位置 字段名 字段值表达式 说明

Header

X-Sid

UUID

安全会话唯一标识,使用UUID即可

Header

Token

<X-Sid>B2S(<ClientPublicKey>)

用于判断传输未被篡改

Body

aesSupports

["AES-ECB-192", "AES-CBC-256"]

支持的算法类型,自然越多越好

Body

publicKey

B2S(<ClientPublicKey>)

客户端公钥的64进制编码

Body

negotiateVersion

"1"

秘钥交换算法版本,可空,默认为 1

服务器-服务端处理协商请求

  1. 收到客户端的协商请求,进行参数校验

    • 参数完整性校验

    • 参数格式校验

    • Token 有效性校验

  2. 根据 negotiateVersion 版本,做一些事情,这里只有ECDH,直接进行ECDH的椭圆曲线选择过程,简单起见,默认使用 ANSI X9.62 Prime 256v1

  3. 生成自己的公私钥对(ServerPublicKey、ServerPrivateKey)

  4. 对 publicKey 64进制解码得到客户端公钥值 ClientPublicKey

  5. 根据 ServerPublicKey、ServerPrivateKey、ClientPublicKey 生成共享密钥 shareKey

  6. 根据 ServerPublicKey、ClientPublicKey 生成共享加密矢量 shareIv

  7. 将 aesSupports 与自己支持的aes加密算法列表做交集处理,在交集中随机选择一个作为本次协商的加密规则 aesAlgorithm

  8. 生成一个协商有效时间 expireTime,如:30分钟

  9. 缓存协商结果

    • 包括:X-Sid、aesAlgorithm、shareKey、shareIv、expireTime

  10. 生成一个双向认证用的Token。

    • Token的签名为:X-Sid + B2S(ServerPublicKey) + AES-param + expireTime

  11. 返回协商结果。

Table 2. 响应协商请求结果
位置 字段名 字段值表达式 or 举例 说明

Header

X-Sid

UUID

安全会话唯一标识

Header

Token

X-Sid + B2S(ServerPublicKey) + AES-param + expireTime

用于判断传输未被篡改

Body

aesAlgorithm

"AES-CBC-256"

本次会话使用的加密算法

Body

publicKey

B2S(<ServerPublicKey>)

服务端公钥的64进制编码

Body

expireTime

1800

会话过期时间,秒

客户端-处理协商返回结果

  1. 收到服务端的协商请求,进行 Token 有效性校验

  2. 根据 ServerPublicKey、ClientPublicKey、ClientPrivateKey 生成共享密钥 shareKey

  3. 根据 ServerPublicKey、ClientPublicKey 生成共享加密矢量 shareIv

  4. 缓存协商结果

    • 包括:X-Sid、aesAlgorithm、shareKey、shareIv、expireTime

至此,协商阶段结束。为了保证客户端发起时,服务端信息未失效,故设计中客户端缓存需提前过期。如 expireTime /= 2 或减少固定时间等。

客户端-发起数据传输请求

  1. 查找是否已与目标服务有进行协商且未过期

    • 未过期则使用

    • 否则重新进行协商 include::[tags=encrypt]

Table 3. 响应协商请求结果
位置 字段名 字段值表达式 or 举例 说明

Header

X-Sid

UUID

安全会话唯一标识

Header

X-Dk

Random

数据秘钥密文64进制编码

Header

Token

X-Sid + dataCipher

用于判断传输未被篡改

Body

aesAlgorithm

"AES-CBC-256"

本次会话使用的加密算法

Body

publicKey

B2S(ServerPublicKey)

服务端公钥的64进制编码

Body

expireTime

1800

会话过期时间,秒

为了避免客户请求前未过期,收到服务端响应时缓存意外过期情况。可以在请求前通过快照机制处理,不推荐判断是否 即将过期 (过期时间小于接口最大超时时间)。

服务端-数据传输处理

处理前解密

include::[tags=decrypt]

业务处理

业务处理后响应前加密

注:与客户端请求前加密流程相同

include::[tags=encrypt]

客户端-处理服务端响应

include::[tags=decrypt]

业务处理

  1. 随机生成 数据秘钥(dataKey,也成临时加密秘钥)

  2. 以 dataKey 为密钥, shareIv 为加密矢量,采用 aesAlgorithm 对应算法加密 敏感数据(data),并对其进行64进制编码得到敏感数据密文(dataCipher)

  3. 以 shareKey 为密钥,shareIv`为加密矢量,采用 `aesAlgorithm 对应算法加密 数据秘钥(dataKey),并对其进行64进制编码得到数据秘钥密文(dataKeyCipher)

  4. 生成认证Token,签名为:X-Sid + dataCipher

  1. 校验请求合法性

    • 校验 Header 中字段的完整性

    • 校验Token的合法性

  2. 判断 X-Sid 对应的协商是否过期,如果过期,直接返回错误码,提示需要重新协商。

  3. 从 X-Sid 对应的缓存中取出协商缓存

  4. 对数据秘钥密文 dataKeyCipher base64解码,再根据 共享秘钥 shareKey、加密矢量 shareIv 以及加密算法 aesAlgorithm 对其解密得到 数据秘钥(dataKey)。

  5. 对敏感数据密文 dataCipher base64解码,再根据 数据秘钥 dataKey、加密矢量 shareIv 以及加密算法 aesAlgorithm 对其解密得到 敏感数据明文(data)