开发指引
更新时间: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请求方法\n2URL\n3请求时间戳\n4请求随机串\n5请求体\n
- 请求方法为:GET,POST。
- URL为请求的绝对路径,包括请求的查询参数,不包括域名部分。
- 请求时间戳为发起请求的UNIX时间戳,单位是秒。
- 请求随机串为一个随机的字符串,包括数字和字母。
- GET方法的请求体为空,POST方法的请求体为发送的JSON报文。
# 3.2.2. 计算签名
包括2步:
- 对签名串计算SM2签名(使用RS_ASN1模式,并且签名者ID为SM2标准默认值“1234567812345678”)。
- 将签名值以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 python32#-*- coding: utf-8 -*-3#4# 依赖以下模块:5# - gmssl67import base648import sys9from gmssl import sm2, func1011def 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))2021def 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'))) 2526def 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进制小写表示的私钥3435 # 构造请求签名串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 04142if __name__ == '__main__':43 sys.exit(main())
# 3.2.5. 一个例子
假设一些输入参数如下:
- 公钥(16进制小写表示):04d3dfbe659754ef9cfc417502d223d81da18c53b5a61a75234ec9996c4c23a1920b5603ee254a38547bf321f060d7461d485f23cafcde8fd844765ca8c628d293
- 私钥(16进制小写表示):d9cda240b9d3ef5dd9593cfd3c78e4274f6dc8c592c0d45fcb7955d44e3e892c
- 时间戳:1661776967
- 随机串:5f270f2ff52b0c67dd47cd5c3ee17e91
如果一个请求如下:
1POST /v3/endowmentins/calc/plus2Content-Type: application/json34{ "a": 1, "b": 2 }
则签名串如下:
1POST\n2/v3/endowmentins/calc/plus\n31661776967\n45f270f2ff52b0c67dd47cd5c3ee17e91\n5{ "a": 1, "b": 2 }\n
则计算的签名值如下:
1MEUCIQDrds++VCEQYmrVnfhLZ6/gr1qwIwN3inK1QSxd0ITP9QIgVGbl1DlOBBP1Yp1WfoZ5ALEe7AVnC0ufmIXt/TdtCYE=
# 3.3. 响应的签名
# 3.3.1. 构造签名串
签名串一共3行,每行以"\n"结尾,如下所示:
1响应时间戳\n2响应随机串\n3响应报文\n
# 3.3.2. 计算签名
包括2步:
- 对签名串计算SM2签名(使用RS_ASN1模式,并且签名者ID为SM2标准默认值“1234567812345678”)。
- 将签名值以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\n25f270f2ff52b0c67dd47cd5c3ee17e91\n3{ "a": 1, "b": 2 }\n
则计算的签名值如下:
1MEQCID83dZssaqU8UBUk0PtrXx4nSphH1SwzqRaNP9Rp6jWpAiAL1I/pAVJvo0BMWzGE9RpC5a6mHCSsgpEZj2rs1X+cNg==
# 3.4. 秘钥的轮换
各自的秘钥在发现有泄露的风险时应通知对方更换,更换流程如下:
- 生成新的秘钥和公钥,并制定好对应的版本号。
- 变更系统支持校验新版公钥生成的签名。
- 通知对方使用新的公钥和对应的版本号。
- 对方变更系统开始灰度使用新版公钥生成签名(如失败可采用旧版公钥生成签名重试)。
- 观察新版公钥鉴权无问题后,双方下线旧版秘钥和公钥。
# 3.5. 签名工具
为方便自测,我们提供了计算签名/加解密工具(点击下载) (opens new window)。