开发指引

更新时间:2024.12.25

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

# 3.2.5. 一个例子

假设一些输入参数如下:

  • 公钥(16进制小写表示):04d3dfbe659754ef9cfc417502d223d81da18c53b5a61a75234ec9996c4c23a1920b5603ee254a38547bf321f060d7461d485f23cafcde8fd844765ca8c628d293
  • 私钥(16进制小写表示):d9cda240b9d3ef5dd9593cfd3c78e4274f6dc8c592c0d45fcb7955d44e3e892c
  • 时间戳:1661776967
  • 随机串:5f270f2ff52b0c67dd47cd5c3ee17e91

如果一个请求如下:

1POST /v3/endowmentins/calc/plus
2Content-Type: application/json
3
4{ "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. 签名工具

为方便自测,我们提供了计算签名/加解密工具(点击下载) (opens new window)

微信支付文档中心已升级,你当前所查看的是旧文档中心的内容,旧文档中心将于 2025年 3 月 31日 下线,请移步 [新文档中心] 查看相应的内容