qingwg / payjs

个人支付收款解决方案PayJS的Golang版本SDK
https://payjs.qingwuguo.com
MIT License
106 stars 23 forks source link

求助:SDK签名验证算法Bug #2

Open qingwg opened 5 years ago

qingwg commented 5 years ago

求助:此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
xluohome commented 5 years ago

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

qingwg commented 5 years ago

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

xluohome commented 5 years ago

可否贴出 response 的 json 字符串 ?

a7a2 commented 5 years ago

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

qingwg commented 5 years ago

@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 字符串

xluohome commented 5 years ago

生成这个 sign 的key 多少。

PHPOSS Team!

------------------ 原始邮件 ------------------ 发件人: "卿务国"notifications@github.com; 发送时间: 2019年6月26日(星期三) 上午10:40 收件人: "qingwg/payjs"payjs@noreply.github.com; 抄送: ""phposs@qq.com; "Mention"mention@noreply.github.com; 主题: 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.

qingwg commented 5 years ago

@xluohome 我发你这个邮箱了:phposs@qq.com

markity commented 4 years ago

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

markity commented 4 years ago

你可以用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 commented 4 years ago

刚才用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