首页 > 经验记录 > 金融涉密级双向加解密架构 —— 混合加密(Hybrid Encryption)

金融涉密级双向加解密架构 —— 混合加密(Hybrid Encryption)

加解密是个常见需求,但是你真要把这个加密方案,设计得像 TLS 那般严谨的话,一般业务是用不上的。哪怕是我们常见的微信支付宝等第三方 API 对接也是。

但是总有一些场景用得上,比如涉密、金融,在这些领域你必须要把整个加解密链条做到完美。在这些极度要求安全性的通信场景里,一般来说,你必须保证数据的机密性、完整性、身份认证、还有关键的加解密性能。

要满足这一套逻辑,业界其实是有成熟方案的,那就是:混合加密(Hybrid Encryption),他的逻辑是:

  1. 使用非对称加密(RSA/SM2) 传输一个对称密钥(Session Key)

  2. 后续数据传输使用对称加密(AES/SM4),提高性能。

 

非对称加解密性能很捉急,性能瓶颈主要就就在这玩意上面。

单独使用非对称加密还有个增加带宽成本的问题,因为非对称加密数据长度远超明文,你的流量费会显著增加。

所以我们才要去用速度快的对称加密,去加密真实业务数据,而用非对称加密,去加密那个对称加密的 SessionKey(临时会话密钥) ,因为 Session Key 长度不长,所以非对称加解密的性能消耗可以接受。同时由于 Session Key 在每次传输时随机生成,我们还可以确保他的安全性。

你如果对接过银行的接口,相信你会很熟悉这一套东西。

国内银行都用国密,基本就是这一套SM2 + SM4 + Hash 组合的混合加密解决方案:

  1. SM2(非对称加密):用于安全传输 SM4 会话密钥

  2. SM4(对称加密):用于高效加密业务数据。

  3. SHA-256(哈希):用于完整性校验,加速签名验签。

 

下面让我来基于此,提供一套可用的,能直接上金融生产环境的完整加解密架构。传输的业务数据、附带数据,我会在本篇博客最后进行解释,包括密钥的分发处理也是。

 

加解密架构

请求流程(Client → Server)

  1. 客户端生成 SM4 Session Key(随机 128 位)。
  2. 使用 SM4 Key 加密业务数据(Base 64 处理)。
  3. 使用 SM2 公钥 加密 SM4 Key(Base 64 处理)。
  4. 计算 dataSHA-256 哈希值
  5. 使用 SM2 私钥dataHash + requestId + sessionKey + timestamp 进行签名
  6. sessionKeydatadataHashsignature 等放入请求 JSON

 

响应流程(Server → Client)

  1. 服务器用 SM2 私钥 解密 sessionKey,获取 SM4 Key
  2. 服务器用 SM4 Key 解密 data,获取业务数据
  3. 服务器计算 dataSHA-256,比对完整性
  4. 服务器使用 SM2 公钥 验证 signature,确保数据未被篡改。
  5. 服务器处理业务逻辑后,按相同方式加密响应数据

 

问:为什么要附带个 base64 编解码?

答:因为不用的话,JSON 数据会失真,除非你传输的是纯二进制数据,其他的我只能说都建议你走一道 base 64

 

伪代码示例

加密请求

// 1. 生成随机 SM4 Key
byte[] sm4Key = generateSM4Key();

// 2. 使用 SM4 加密数据
String encryptedData = sm4Encrypt(sm4Key, businessData);

// 3. 使用 SM2 公钥加密 SM4 Key
String encryptedSessionKey = sm2Encrypt(serverPublicKey, sm4Key);

// 4. 计算数据哈希值
String dataHash = sha256(encryptedData);

// 5. 生成签名(SM2 私钥)
String signature = sm2Sign(clientPrivateKey, dataHash + requestId + encryptedSessionKey + timestamp);

// 6. 发送请求 JSON
sendRequest({
    "requestId": requestId,
    "timestamp": timestamp,
    "appId": "client-abc123",
    "version": "1.0",
    "sessionKey": encryptedSessionKey,
    "data": encryptedData,
    "dataHash": dataHash,
    "signature": signature
});

 

服务器解密

// 1. 使用 SM2 私钥解密 Session Key
byte[] sm4Key = sm2Decrypt(serverPrivateKey, encryptedSessionKey);

// 2. 使用 SM4 解密数据
String decryptedData = sm4Decrypt(sm4Key, encryptedData);

// 3. 计算 Hash 并比对完整性
if (!sha256(decryptedData).equals(dataHash)) {
    throw new SecurityException("数据篡改");
}

// 4. 验证 SM2 签名
if (!sm2Verify(clientPublicKey, dataHash + requestId + encryptedSessionKey + timestamp, signature)) {
    throw new SecurityException("签名校验失败");
}

 

具体请求说明

根据上边的流程,你的请求 JSON 一般是这样的

{
  "requestId": "uuid-123456",
  "timestamp": 1700000000,
  "appId": "test",
  "version": "1",
  "sessionKey": "Base64(SM2加密的SM4会话密钥)",
  "data": "Base64(SM4加密的业务数据)",
  "dataHash": "SHA256(data原始内容)",
  "signature": "Base64(SM2签名(dataHash + requestId + timestamp + sessionKey))"
}

具体来讲,会有一个随机生成的 requestId 用来定位响应数据,同时保证一个请求的唯一性和幂等性

而 timestamp 是用来校验时钟,同时用来支持超时机制

appId 用来作为客户端标识,version 可能是你请求接口的版本

这些都是位于具体加解密流程之外的,就算被中间人截取了,也无所谓的数据。而其他 sessionKey 之类的就不解释了,都在上面说过。

 

接着,下面是一个响应 JSON 示例,你可以作为参考。实际上请求、响应的 JSON 字段里,除了那几个必要参数,其他都是可以动态调整的。

{
  "requestId": "uuid-123456",
  "timestamp": 1700000001,
  "appId": "test",
  "version": "1",
  "sessionKey": "Base64(SM2加密的SM4会话密钥)",
  "code": "200",
  "message": "Success",
  "data": "Base64(SM4加密的业务数据)",
  "dataHash": "SHA256(data原始内容)",
  "signature": "Base64(SM2签名(dataHash + requestId + timestamp + sessionKey))",
  "success": true
}

 

 

密钥对处理

这个东西一定得谨慎,在混合加密的架构下,双方都持有各自的密钥对(公钥 + 私钥)。

加密:通信一方使用对方的公钥加密数据。

解密:接收方使用自己的私钥解密数据。

 

服务器下发给客户端:

客户端的私钥(Client PrivateKey)

服务器的公钥(Server PublicKey)

 

服务器存储:

服务器的公钥(Server PublicKey)

服务器的私钥(Server PrivateKey)

客户端的公钥(Client PublicKey)

 

服务器不应该存储客户端的私钥,因为客户端的私钥是客户端的身份凭据,服务器存储它会带来隐私泄露和被盗用的风险。

一旦服务器被攻破,攻击者可以冒充任何客户端,导致身份伪造,非常危险。

 

 

           


CAPTCHAis initialing...
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00