美文网首页
支付宝公钥、私钥和沙箱环境

支付宝公钥、私钥和沙箱环境

作者: 黑夜的眸 | 来源:发表于2018-08-21 11:23 被阅读0次

    使用蚂蚁金服开放平台的沙箱环境

    第一步:获取公钥、私钥

    首先,进入蚂蚁金服开放平台官方主页, 点击文档中心的开发文档,往下翻,找到开发工具-沙箱环境

    进入沙箱环境页面,系统已经自动为你创建一个应用,在基础信息中可以看到应用信息。


    这里RSA2公钥我已经生成了,新用户可点击开发文档/ 签名专区 / 生成RSA密钥
    查看帮助文档来生成
    支付宝提供一键生成工具便于开发者生成一对RSA密钥,可通过下方链接下载密钥生成工具:
    WINDOWS
    MAC_OSX
    下载该工具后,解压打开文件夹,运行“RSA签名验签工具.bat”(WINDOWS)或“RSA签名验签工具.command”(MAC_OSX)。
    界面示例如下,注意python语言密钥格式选择PKCS1(非JAVA适用),密钥长度选择更安全的2048


    将私钥和公钥都复制到项目应用里存放,存为txt文件即可,文本内容在首尾分别加上“-----BEGIN PRIVATE KEY-----”和“-----END PRIVATE KEY-----”
    -----BEGIN PRIVATE KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwlSXWV1bGwcZf720nUMskUGoWbyPIXm/P+yvPExy1SQHltmFncuYNB53Pk/kWiO3iP4aHgDfh3Dkw9JbLqrpNzM5ch/QfEIAY7jy15yPg81DrBNP0Sh+AbPzjMB/TyO/tIc5kzBhDZ9jXes6VybvYppAlYxzILWmA5nM5dPHQUn1Wpm7oQbuLUy1EKP0aW3R/lFIWzLuSAQvh4EExJXKGvH1M65TN5gwBFibSVpwo6YVsYJfx3zhR6JbO3bc5er1xzjHo0jffQ7ujE6Gys5VV2k6F2Spu6nAshBrxP9L+kI9aO4zKMAulthbFueHkaJGowzboNk1IE3uKnvxHN6CiQIDAQAB
    -----END PRIVATE KEY-----
    

    支付宝的公钥在沙箱环境获得,也一并存到项目下以后验证时要用


    第二步:查看API文档

    1.首先还是进入蚂蚁金服开放平台官方主页
    点击文档中心的开发文档,往下翻,找到左侧产品文档的电脑网站支付,选择API列表,进入统一收单下单支付页面接口查看文档



    网关



    公共参数,重点关注以下字段


    • appid :2016091700535495(填写沙箱应用的appid)
    • return_url: 对于PC网站支付的交易,在用户支付完成之后,支付宝会根据API中商户传入的return_url参数,通过GET请求的形式将部分支付结果参数通知到商户系统, 即同步返回地址,即支付成功跳转的页面url。同步通知详情
    • sign 签名字段,最重要的字段,后面会讲,详见签名
    • notify_url: 对于PC网站支付的交易,在用户支付完成之后,支付宝会根据API中商户传入的notify_url,通过POST请求的形式将支付结果作为参数通知到商户系统。异步通知详情
    • biz_content:除公共参数以外的其他必要参数

    请求参数这些字段是最后放入公共字段里的biz_content,着重关注必填项

    第三步:签名与验签

    签名由于SDK没有python,所以自行实现签名,点击下方此处流程

    1.筛选并排序
    获取所有请求参数,不包括字节类型参数,如文件、字节流,剔除sign字段,剔除值为空的参数,并按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。

    2.拼接
    将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。
    例如下面的请求示例,参数值都是示例,开发者参考格式即可:

    REQUEST URL: https://openapi.alipay.com/gateway.do
    REQUEST METHOD: POST
    CONTENT:
    app_id=2014072300007148
    method=alipay.mobile.public.menu.add
    charset=GBK
    sign_type=RSA2
    timestamp=2014-07-24 03:07:50
    biz_content={"button":[{"actionParam":"ZFB_HFCZ","actionType":"out","name":"话费充值"},{"name":"查询","subButton":[{"actionParam":"ZFB_YECX","actionType":"out","name":"余额查询"},{"actionParam":"ZFB_LLCX","actionType":"out","name":"流量查询"},{"actionParam":"ZFB_HFCX","actionType":"out","name":"话费查询"}]},{"actionParam":"http://m.alipay.com","actionType":"link","name":"最新优惠"}]}
    sign=e9zEAe4TTQ4LPLQvETPoLGXTiURcxiAKfMVQ6Hrrsx2hmyIEGvSfAQzbLxHrhyZ48wOJXTsD4FPnt+YGdK57+fP1BCbf9rIVycfjhYCqlFhbTu9pFnZgT55W+xbAFb9y7vL0MyAxwXUXvZtQVqEwW7pURtKilbcBTEW7TAxzgro=
    version=1.0
    

    则待签名字符串为下图:可以看出字段进行了排序并且完成了拼接

    app_id=2014072300007148&biz_content={"button":[{"actionParam":"ZFB_HFCZ","actionType":"out","name":"话费充值"},{"name":"查询","subButton":[{"actionParam":"ZFB_YECX","actionType":"out","name":"余额查询"},{"actionParam":"ZFB_LLCX","actionType":"out","name":"流量查询"},{"actionParam":"ZFB_HFCX","actionType":"out","name":"话费查询"}]},{"actionParam":"http://m.alipay.com","actionType":"link","name":"最新优惠"}]}&charset=GBK&method=alipay.mobile.public.menu.add&sign_type=RSA2&timestamp=2014-07-24 03:07:50&version=1.0
    

    3.调用签名函数
    使用各自语言对应的SHA256WithRSA(对应sign_type为RSA2)或SHA1WithRSA(对应sign_type为RSA)签名函数利用商户私钥对待签名字符串进行签名,并进行Base64编码。

    4.把生成的签名赋值给sign参数,拼接到请求参数中。


    验签

    1.在通知返回参数列表中,除去signsign_type两个参数外,凡是通知返回回来的参数皆是待验签的参数。

    2.将剩下参数进行url_decode, 然后进行字典排序,组成字符串,得到待签名字符串:

    3.将签名参数(sign)使用base64解码为字节码串。

    4.使用RSA2的验签方法,通过签名字符串、签名参数(经过base64解码)及支付宝公钥验证签名,根据返回结果判定是否验签通过。


    代码段

    import json
    
    from datetime import datetime
    from Crypto.PublicKey import RSA
    from Crypto.Signature import PKCS1_v1_5
    from Crypto.Hash import SHA256
    from base64 import b64encode, b64decode
    from urllib.parse import urlparse, parse_qs, quote_plus
    
    
    class Alipay(object):
        def __init__(self, app_id, app_private_key_path, alipay_public_key_path, debug=True, return_url=None, notify_url=None):
            self.app_id = app_id
            self.debug = debug
            self.return_url = return_url
            self.notify_url = notify_url
    
            with open(app_private_key_path) as fp:
                self.app_private_key = RSA.importKey(fp.read())
            with open(alipay_public_key_path) as fp:
                self.alipay_public_key = RSA.importKey(fp.read())
    
            if debug:
                self.__gateway = "https://openapi.alipaydev.com/gateway.do"
            else:
                self.__gateway = "https://openapi.alipay.com/gateway.do"
    
        def build_url(self, biz_content):
            """
            :param biz_content: 请求参数部分
            :return: 完整的访问地址url字符串
            """
            # 初始化必填公共字段,sign除外
            data = {
                "app_id": self.app_id,
                "method": "alipay.trade.page.pay",
                "charset": "utf-8",
                "sign_type": "RSA2",
                "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                "version": "1.0",
                "biz_content": biz_content
            }
            if self.notify_url:
                data["notify_url"] = self.notify_url
            if self.return_url:
                data["return_url"] = self.return_url
    
            # 对待签名字符串进行RSA签名
            sign_str = self.get_request_url_str(data, flag=False)
            sign = self.rsa_sign(sign_str.encode("utf-8"))
    
            # 将签好名的sign字段拼接到请求字符串最后,字符串需对斜线进行编码
            request_url_str = self.get_request_url_str(data)
            request_url_str += "&sign={}".format(quote_plus(sign))
            print(self.__gateway + '?' + request_url_str)
            return self.__gateway + '?' + request_url_str
    
        def translate_url(self, returl_url):
            """
            :param returl_url:返回字符串
            :return: 解析出请求参数字符串及签名
            """
            o = urlparse(return_url)
            query = parse_qs(o.query)
            processed_query = {}
    
            ali_sign = query.pop("sign")[0] if "sign" in query else None
    
            if "sign_type" in query:
                sign_type = query.pop("sign_type")[0]
            for key, value in query.items():
                processed_query[key] = value[0]
            request_url_str = self.get_request_url_str(processed_query, flag=False).encode("utf-8")
            # 将解析后的request_url_str字符串和签名参数拼接成元祖
            data = ([request_url_str, ali_sign])
            return data
    
    
        def get_biz_content(self, out_trade_no, total_amount, subject, **kwargs):
            """
            获取请求参数
            """
            biz_content = {
                "out_trade_no": out_trade_no,
                "product_code": "FAST_INSTANT_TRADE_PAY",
                "total_amount": total_amount,
                "subject": subject,
            }
            biz_content.update(kwargs)
            return biz_content
    
        def get_request_url_str(self, data, flag=True):
            """
            :param data: 获取的字典型请求参数
            :param flag: 是否对字典的值进行编码斜线
            :return:排好序并用&进行拼接的url字符串
            """
            #对键值为字典的转化为str
            for k, v in data.items():
                if isinstance(v, dict):
                    data[k] = json.dumps(v)
            # 按key值字母升序排序为列表,方便拼接
            data_lst = sorted([(k, v) for k, v in data.items()])
            if flag:
                request_url_str = "&".join(["{k}={v}".format(k=k, v=quote_plus(v)) for k, v in data_lst])
            else:
                request_url_str = "&".join(["{k}={v}".format(k=k, v=v) for k, v in data_lst])
            return request_url_str
    
        def rsa_sign(self, data):
            """
            利用商户私钥对待签名字符串进行签名
            :param data: 待签名data,格式bytes
            :return: 生成的签名字符串
            """
            private_key = self.app_private_key
            hash_obj = SHA256.new(data)
            signer = PKCS1_v1_5.new(private_key)
            signature = signer.sign(hash_obj)
            sign = b64encode(signature).decode("utf-8")
            return sign
    
        def rsa_verify(self, raw_data, signature):
            """
            使用RSA2的验签方法,通过签名字符串、签名参数(经过base64解码)及支付宝公钥验证签名,
            根据返回结果判定是否验签通过
            :param raw_data: 原签名字符串data,格式bytes
            :param signature: 签名参数
            :return: 验签是否通过
            """
            public_key = self.alipay_public_key
            hash_obj = SHA256.new(raw_data)
            verifier = PKCS1_v1_5.new(public_key)
    
            return verifier.verify(hash_obj, b64decode(signature.encode("utf-8")))
    
    
    if __name__ == "__main__":
        alipay = Alipay(
            app_id="2016091700535495",
            return_url="http://101.200.32.20:8000/",
            notify_url="http://projectsedus.com/",
            app_private_key_path="../trade/keys/private_2048.txt",
            alipay_public_key_path="../trade/keys/alipay_keys_2048.txt",
        )
        content = alipay.get_biz_content(out_trade_no="201802021232", total_amount=1, subject="测试支付")
        alipay.build_url(content)
    
        # 根据return_url获取请求参数
        return_url = "http://101.200.32.20:8000/?charset=utf-8&out_trade_no=201702021230&method=alipay.trade.page.pay.return&total_amount=1.00&sign=gvXy%2FXOSOUQupNTn%2Fi%2BdkpqqcDc0Ia8R5lZ2kQRl5UcVb6w83pJqadDPzjdCTLq7%2F4aoIEHhAR6%2FOZwXmsEGWGd2DNynjiGF2mzIpKjDIaGLhdlP9DMIVy%2BIuCfLB7tHC9n1%2BPYoH1YYVWIqO%2B5FLcTh77ucwP66Glq5gFBKpD0b6fiPYlAjxO%2FAIEiyRxSaZS3fj%2BBOhB%2Fo31rjMLa5EOHgvdDeP8OTa01VVJ3wAMBDHyGslj5cShZQwdALyuxjGTFOb0PHFiWEjS828j7ZiTkWiODZ2QFClLE9NHwR6VrlnT0Ttss9Hfu8zwIBMsK3SWt9f32TnmC2wq7hkvMsyg%3D%3D&trade_no=2018082021001004950200556565&auth_app_id=2016091700535495&version=1.0&app_id=2016091700535495&sign_type=RSA2&seller_id=2088102176097894&timestamp=2018-08-20+23%3A23%3A12"
        data = alipay.translate_url(return_url)
        print(alipay.rsa_verify(data[0], data[1]))
    

    相关文章

      网友评论

          本文标题:支付宝公钥、私钥和沙箱环境

          本文链接:https://www.haomeiwen.com/subject/idfdiftx.html