payjs icon indicating copy to clipboard operation
payjs copied to clipboard

求助:SDK签名验证算法Bug

Open qingwg opened this issue 7 years ago • 10 comments

求助:此SDK签名验证算法与微信相同。但是如果碰到[]string等,或多维结构体,例如JSAPI支付接口的Response,则无法正确签名验证。该如何处理呢?

目前有存在多为结构签名验证的地方都取消了验证判断

分别是: JSAPI支付接口

获取用户详情

异步通知

签名验证代码则如下:

签名验证代码

代码有点乱,一直在调试。这段代码无法正确验证多维结构,例如无法验证下面结构:

// JsApiResponse
type JsApiResponse struct {
	ReturnCode   int    `json:"return_code"`    //Y	0:失败 1:成功
	ReturnMsg    string `json:"return_msg"`     //Y	失败原因
	PayJSOrderID string `json:"payjs_order_id"` //Y	PAYJS 侧订单号
	JsApi        JsApi  `json:"jsapi"`          //N	用于发起支付的支付参数
	Sign         string `json:"sign"`           //Y	数据签名
}

// JsApi
type JsApi struct {
	AppID     string `json:"appId"`
	TimeStamp string `json:"timeStamp"`
	NonceStr  string `json:"nonceStr"`
	Package   string `json:"package"`
	SignType  string `json:"signType"`
	PaySign   string `json:"paySign"`
}

官方给出的回复为:

拼接方法形如:
user[name]=&user[age]=test2&user[sex]=test3,
在php中直接使用http_build_query实现的,
如其他语言请参照该方法:http://php.net/manual/zh/function.http-build-query.php

qingwg avatar Feb 20 '19 09:02 qingwg

这部分工作还没有完成吗? 我这边实现过支付宝api接口的response的签名验证。

xluohome avatar May 23 '19 09:05 xluohome

@xluohome 大部分接口签名验证是可以的,但是碰到多维结构的,就无法验证了。就像上面的结构, JsApiResponse 包含一个 JsApi ,然后就验证失败了。

qingwg avatar May 23 '19 09:05 qingwg

可否贴出 response 的 json 字符串 ?

xluohome avatar May 27 '19 08:05 xluohome

提供 “通信密钥"、 "PayJS的商户号”之类必要的东东我来帮你调吧,保证能解!

a7a2 avatar May 30 '19 10:05 a7a2

@xluohome {"return_code":1,"return_msg":"SUCCESS","user":{"subscribe":1,"openid":"o7LFAwXrAJip_0GEhcHWKqM2LPxw","nickname":"卿务国","sex":1,"city":"长沙","country":"中国","province":"湖南","language":"zh_CN","headimgurl":"http://thirdwx.qlogo .cn/mmopen/Z7gAdqBjbbFMvGpwQvsVSVdpxjalBfg6V9cm2svg7yUtFgQjSVOsnYFSBG7401bZJPNoc9ZNfNEC0LgtBtQO8DaCtcu1icOnL/132","subscribe_time":1544522310,"remark":"","groupid":0,"tagid_list":[],"subscribe_scene":"ADD_SCENE_SEARCH","qr_scene":0, "qr_scene_str":""},"sign":"3C0427D9D501D856F29274B9EF11F995"} 不好意思,这么晚回复。 上面就是用户获取详情接口的 response 的 json 字符串

qingwg avatar Jun 26 '19 02:06 qingwg

生成这个 sign 的key 多少。

PHPOSS Team!

------------------ 原始邮件 ------------------ 发件人: "卿务国"[email protected]; 发送时间: 2019年6月26日(星期三) 上午10:40 收件人: "qingwg/payjs"[email protected]; 抄送: ""[email protected]; "Mention"[email protected]; 主题: Re: [qingwg/payjs] 求助:SDK签名验证算法Bug (#2)

@xluohome {"return_code":1,"return_msg":"SUCCESS","user":{"subscribe":1,"openid":"o7LFAwXrAJip_0GEhcHWKqM2LPxw","nickname":"卿务国","sex":1,"city":"长沙","country":"中国","province":"湖南","language":"zh_CN","headimgurl":"http://thirdwx.qlogo .cn/mmopen/Z7gAdqBjbbFMvGpwQvsVSVdpxjalBfg6V9cm2svg7yUtFgQjSVOsnYFSBG7401bZJPNoc9ZNfNEC0LgtBtQO8DaCtcu1icOnL/132","subscribe_time":1544522310,"remark":"","groupid":0,"tagid_list":[],"subscribe_scene":"ADD_SCENE_SEARCH","qr_scene":0, "qr_scene_str":""},"sign":"3C0427D9D501D856F29274B9EF11F995"} 不好意思,这么晚回复。 上面就是用户获取详情接口的 response 的 json 字符串

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

xluohome avatar Jun 26 '19 03:06 xluohome

@xluohome 我发你这个邮箱了:[email protected]

qingwg avatar Jun 26 '19 03:06 qingwg

校验响应得到的bytes数据可以试着调用动态语言吗, 我发现golang很难完成这个工作

markity avatar Jan 28 '20 06:01 markity

你可以用go调用python, 我刚才实现了一下, 应该能用

import json
import hashlib
import importlib
import sys

json_str = '{"name":"markity","tags":["golang","python"],"detail":{"age":16,"phone":"11011011011", "birthday":"88888888"},"sign":"3123123"}'
mchKey = "demoMchKey"

try:
    json_obj = json.loads(json_str)
except:
    sys.exit(1)

keys = list(json_obj.keys())

# 对于某些特殊情况, 如重复退款, 即使return_code为1也无sign
if "sign" not in keys:
    print("true")
    sys.exit(0)

origin_sign = json_obj["sign"]
keys.remove("sign")
keys.sort()


def walk(perfix, l, keys, struct):
    # walk 迭代遍历, 将key=val字符串追加入l列表中
    for k in keys:
        v = struct[k]
        if v == None:
            continue
        if type(v) == dict:
            _keys = list(v.keys())
            _keys.sort()
            walk(k, kvList, _keys, v)
            continue
        if type(v) == list:
            walk(k, kvList, range(len(v)), v)
            continue
        if perfix == "":
            kvList.append("{}={}".format(k, v))
        if perfix != "":
            kvList.append("{}[{}]={}".format(perfix, k, v))


kvList = []
walk("", kvList, keys, json_obj)
kvChain = '&'.join(kvList)
print(kvChain)

realSign = hashlib.md5(kvChain.encode("utf-8")).hexdigest().upper()
print(realSign)

输出

detail[age]=16&detail[birthday]=88888888&detail[phone]=11011011011&name=markity&tags[0]=golang&tags[1]=python
782F7639850D47C4C79758B412D51B89

markity avatar Jan 28 '20 10:01 markity

刚才用go重写了一遍, 你看看

package main

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"sort"
	"strings"
)

var jsonStr = `
{
	"name":"Markity",
	"age":16,
	"interests":["python","golang"],
	"friends":[
		{"name":"Jack","age":17,"interests":["lua"]},
		{"name":"Mary","age":15,"interests":["java"]}
	],
	"compary":
	{
		"name":"Microsoft",
		"workders":[
			{"name":"Peter","age":22},
			{"name":"Eric","age":23}
		]
	},
	"sign":"XXX"
}
`

var mchKey = "xxxxx"

func main() {
	jsonObj := make(map[string]interface{})
	json.Unmarshal([]byte(jsonStr), &jsonObj)

	_, ok := jsonObj["sign"]
	// 某些特殊情况如重复退款, 即使return_code为1, 也没有sign, 这时直接return true
	if !ok {
		fmt.Println("true")
		return
	}
	originSign := jsonObj["sign"].(string)
	delete(jsonObj, "sign")

	// 将key=val追加入kvList中
	kvList := make([]string, 0)
	var walkMap func(string, interface{}, interface{}) = nil
	walkMap = func(perfix string, obj interface{}, ks interface{}) {
		// obj为[]interface{}或者map[string]interface{}类型, ks为[]string或者[]int类型
		switch keys := ks.(type) {
		case []string:
			// 当ks为[]string类型时, obj为map[string]interface{}类型
			object := obj.(map[string]interface{})
			for _, key := range keys {
				v := object[key]
				// 根据签名算法, 值为null的不计入
				if v == nil {
					continue
				}
				switch value := v.(type) {
				// 若v为复合类型, 进入继续遍历
				case []interface{}:
					_perfix := ""
					if perfix == "" {
						_perfix = key
					} else {
						_perfix = fmt.Sprintf("%v[%v]", perfix, key)
					}
					_keys := make([]int, len(value))
					for i := 0; i < len(value); i++ {
						_keys = append(_keys, i)
					}
					walkMap(_perfix, value, _keys)
					continue
				case map[string]interface{}:
					_perfix := ""
					if perfix == "" {
						_perfix = key
					} else {
						_perfix = fmt.Sprintf("%v[%v]", perfix, key)
					}
					_keys := make([]string, 0)
					for __key, _ := range value {
						_keys = append(_keys, __key)
					}
					sort.Strings(_keys)
					walkMap(_perfix, value, _keys)
					continue
				}
				if perfix == "" {
					kvList = append(kvList, fmt.Sprintf("%v=%v", key, v))
				} else {
					kvList = append(kvList, fmt.Sprintf("%v[%v]=%v", perfix, key, v))
				}
			}
		case []int:
			// 当ks为[]int, obj为[]interface{}
			object := obj.([]interface{})
			for key := 0; key < len(object); key++ {
				v := object[key]
				if v == nil {
					continue
				}
				switch value := v.(type) {
				// 若v为复合类型, 进入继续遍历
				case []interface{}:
					_perfix := fmt.Sprintf("%v[%v]", perfix, key)
					_keys := make([]int, len(value))
					for i := 0; i < len(value); i++ {
						_keys = append(_keys, i)
					}
					walkMap(_perfix, value, _keys)
					continue
				case map[string]interface{}:
					_perfix := fmt.Sprintf("%v[%v]", perfix, key)
					_keys := make([]string, 0)
					for __key, _ := range value {
						_keys = append(_keys, __key)
					}
					walkMap(_perfix, value, _keys)
					continue
				}
			}
		}
	}
	keys := make([]string, 0)
	for key, _ := range jsonObj {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	walkMap("", jsonObj, keys)
	kvList = append(kvList, "key="+mchKey)
	kvChain := strings.Join(kvList, "&")
	fmt.Println(kvChain)
	realSign := fmt.Sprintf("%x", md5.Sum([]byte(kvChain)))
	if realSign == originSign {
		fmt.Println("true")
	} else {
		fmt.Println("false")
	}
}

输出

age=16&compary[name]=Microsoft&compary[workders][0][name]=Peter&compary[workders][0][age]=22&compary[workders][1][name]=Eric&compary[workders][1][age]=23&friends[0][name]=Jack&friends[0][age]=17&friends[1][name]=Mary&friends[1][age]=15&name=Markity&key=xxxxx
false

markity avatar Jan 29 '20 05:01 markity