开发指引

更新时间:2024.12.19

1. 整体设计理念

用户选择银行后,微信养老金需查询银行侧是否存在个养账户。若存在,则在养老保险服务存储该账户信息,完成绑定;若不存在,则在前端提示用户相关信息。

2. 微信与银行交互时序图

3. 通信协议

3.1. 基本规则

3.1.1. 交换格式

微信与银行交互使用HTTPS协议,请求和响应报文都使用JSON作为数据交换格式,都要携带签名信息。

请求至少携带HTTP请求头参数:

  • User-Agent:必填,按实际情况填写。

  • Accept:必填,且值为application/json。

  • Content-Type:当使用POST请求时携带,值为application/json。

  • Authorization:必填,为请求URL及相关参数的签名信息,计算方式见设置Authorization头

  • Request-ID:必填,为请求唯一标识,不超过64个字符,用于定位问题,不可作它用。

响应应至少携带HTTP请求头参数:

  • Content-Type:必填,且值为application/json。

  • WxIns-Nonce:必填,随机串,由数字和字母组成。

  • WxIns-Signature:必填,响应的签名,计算方法见设置响应头

  • WxIns-Timestamp:必填,发送响应的UNIX时间戳,单位是秒。

  • WxIns-Version:必填,秘钥的版本号,用于支持秘钥轮换,首次为1,以后每次更换秘钥递增。

3.1.2. 错误码

处理成功的请求,返回的HTTP状态码为200(如果有响应消息体)或204(如果没有响应消息体),其它状态码表示失败(逻辑失败如输入参数错误、鉴权失败等返回4XX状态码,运行时失败返回5XX),会返回具体的错误信息,包括以下字段:

  • code:详细错误码。

  • message:错误描述,使用易理解的文字表示错误的原因。

示例如下:

1{
2    "code": "Invalid parameters",
3    "message": "输入参数错误",
4}

3.2. 请求的签名

根据请求参数构造签名串后,使用国密算法SM2计算签名(SM2 sign with SM3),并使用Base64编码后作为Authorization的值。详细过程如下:

3.2.1. 构造签名串

签名串一共5行,如下所示,每行以"\n"结束。

1请求方法\n
2URL\n
3请求时间戳\n
4请求随机串\n
5请求体\n
  • 请求方法为:GET,POST。

  • URL为请求的绝对路径,包括请求的查询参数,不包括域名部分。

  • 请求时间戳为发起请求的UNIX时间戳,单位是秒。

  • 请求随机串为一个随机的字符串,包括数字和字母。

  • GET方法的请求体为空,POST方法的请求体为发送的JSON报文。

3.2.2. 计算签名

包括2步:

  1. 对签名串计算SM2签名(使用RS_ASN1模式,并且签名者ID为SM2标准默认值“1234567812345678”)。

  2. 将签名值以Base64编码。

3.2.3. 设置Authorization头

Authorization的值为上述计算的签名信息,包括:

  • 随机串

  • 时间戳

  • 签名

如果是微信发起请求,格式如下:

1Authorization: version="<秘钥版本号>",nonce_str="<计算签名的随机串>",timestamp="<计算签名的时间戳>",signature="<签名值>"

如果是银行发起请求,格式如下:

1Authorization: version="<秘钥版本号>",bank_id="<银行编码>",nonce_str="<计算签名的随机串>",timestamp="<计算签名的时间戳>",signature="<签名值>"

3.2.4. 计算签名和验签的示例代码

注意:

代码仅为示例,不代表真实实现。

1#!/usr/bin/env python3
2#-*- coding: utf-8 -*-
3#
4# 依赖以下模块:
5#   - gmssl
6import base64
7import sys
8from gmssl import sm2, func
9def sign(plain_text, public_key, private_key):
10    # 初始化SM2签名对象
11    sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key, asn1=True, mode=1)
12    # 构造随机字符串
13    random_hex_str = func.random_hex(sm2_crypt.para_len)
14    # 计算签名值,输出为16进制表示
15    sign_hex = sm2_crypt.sign_with_sm3(plain_text.encode('utf-8'), random_hex_str)
16    # 转换为Base64编码
17    return base64.standard_b64encode(bytes.fromhex(sign_hex))
18def verify(plain_text, public_key, private_key, sign_val):
19    decoded_sign_val = base64.standard_b64decode(sign_val).hex()
20    sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key, asn1=True, mode=1)
21    print(sm2_crypt.verify_with_sm3(decoded_sign_val, plain_text.encode('utf-8')))  
22def main():
23    method = sys.argv[1]
24    url_path = sys.argv[2]
25    timestamp = sys.argv[3]
26    nonce = sys.argv[4]
27    body = sys.argv[5]
28    public_key = sys.argv[6]    # 16进制小写表示的公钥
29    private_key = sys.argv[7]   # 16进制小写表示的私钥
30    # 构造请求签名串
31    plain_text = "{}\n{}\n{}\n{}\n{}\n".format(method, url_path, timestamp, nonce, body)
32    sign_val = sign(plain_text, public_key, private_key)
33    print("sign_val: {}".format(sign_val))
34    verify(plain_text, public_key, private_key, sign_val)
35    return 0
36if __name__ == '__main__':
37    sys.exit(main())

3.2.5. 一个例子

假设一些输入参数如下:

  • 公钥(16进制小写表示):04d3dfbe659754ef9cfc417502d223d81da18c53b5a61a75234ec9996c4c23a1920b5603ee254a38547bf321f060d7461d485f23cafcde8fd844765ca8c628d293

  • 私钥(16进制小写表示):d9cda240b9d3ef5dd9593cfd3c78e4274f6dc8c592c0d45fcb7955d44e3e892c

  • 时间戳:1661776967

  • 随机串:5f270f2ff52b0c67dd47cd5c3ee17e91

如果一个请求如下:

1POST /v3/endowmentins/calc/plus
2Content-Type: application/json
3{ "a": 1, "b": 2 }

则签名串如下:

1POST\n
2/v3/endowmentins/calc/plus\n
31661776967\n
45f270f2ff52b0c67dd47cd5c3ee17e91\n
5{ "a": 1, "b": 2 }\n

则计算的签名值如下:

1MEUCIQDrds++VCEQYmrVnfhLZ6/gr1qwIwN3inK1QSxd0ITP9QIgVGbl1DlOBBP1Yp1WfoZ5ALEe7AVnC0ufmIXt/TdtCYE=

3.3. 响应的签名

3.3.1. 构造签名串

签名串一共3行,每行以"\n"结尾,如下所示:

1响应时间戳\n
2响应随机串\n
3响应报文\n

3.3.2. 计算签名

包括2步:

  1. 对签名串计算SM2签名(使用RS_ASN1模式,并且签名者ID为SM2标准默认值“1234567812345678”)。

  2. 将签名值以Base64编码。

3.3.3. 设置响应头

设置如下响应头:

  • WxIns-Nonce:计算签名的随机串。

  • WxIns-Signature:签名值。

  • WxIns-Timestamp:计算签名的时间戳。

  • WxIns-Version:计算签名使用的秘钥对应的版本号,支持秘钥轮换,首次为1,以后每次更换秘钥递增。

3.3.4. 一个例子

假设一些输入参数如下:

  • 公钥(16进制小写表示):04d3dfbe659754ef9cfc417502d223d81da18c53b5a61a75234ec9996c4c23a1920b5603ee254a38547bf321f060d7461d485f23cafcde8fd844765ca8c628d293

  • 私钥(16进制小写表示):d9cda240b9d3ef5dd9593cfd3c78e4274f6dc8c592c0d45fcb7955d44e3e892c

  • 时间戳:1661776967

  • 随机串:5f270f2ff52b0c67dd47cd5c3ee17e91

如果响应如下:

1{ "a": 1, "b": 2 }

则签名串如下:

11661776967\n
25f270f2ff52b0c67dd47cd5c3ee17e91\n
3{ "a": 1, "b": 2 }\n

则计算的签名值如下:

1MEQCID83dZssaqU8UBUk0PtrXx4nSphH1SwzqRaNP9Rp6jWpAiAL1I/pAVJvo0BMWzGE9RpC5a6mHCSsgpEZj2rs1X+cNg==

3.4. 秘钥的轮换

各自的秘钥在发现有泄露的风险时应通知对方更换,更换流程如下:

  1. 生成新的秘钥和公钥,并制定好对应的版本号。

  2. 变更系统支持校验新版公钥生成的签名。

  3. 通知对方使用新的公钥和对应的版本号。

  4. 对方变更系统开始灰度使用新版公钥生成签名(如失败可采用旧版公钥生成签名重试)。

  5. 观察新版公钥鉴权无问题后,双方下线旧版秘钥和公钥。

3.5. 签名工具

为方便自测,我们提供了计算签名/加解密工具: