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

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

第一步:获取公钥、私钥

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

支付宝公钥、私钥和沙箱环境_第1张图片

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


支付宝公钥、私钥和沙箱环境_第2张图片

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

支付宝公钥、私钥和沙箱环境_第3张图片

将私钥和公钥都复制到项目应用里存放,存为txt文件即可,文本内容在首尾分别加上“-----BEGIN PRIVATE KEY-----”和“-----END PRIVATE KEY-----”
支付宝公钥、私钥和沙箱环境_第4张图片

-----BEGIN PRIVATE KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwlSXWV1bGwcZf720nUMskUGoWbyPIXm/P+yvPExy1SQHltmFncuYNB53Pk/kWiO3iP4aHgDfh3Dkw9JbLqrpNzM5ch/QfEIAY7jy15yPg81DrBNP0Sh+AbPzjMB/TyO/tIc5kzBhDZ9jXes6VybvYppAlYxzILWmA5nM5dPHQUn1Wpm7oQbuLUy1EKP0aW3R/lFIWzLuSAQvh4EExJXKGvH1M65TN5gwBFibSVpwo6YVsYJfx3zhR6JbO3bc5er1xzjHo0jffQ7ujE6Gys5VV2k6F2Spu6nAshBrxP9L+kI9aO4zKMAulthbFueHkaJGowzboNk1IE3uKnvxHN6CiQIDAQAB
-----END PRIVATE KEY-----

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


支付宝公钥、私钥和沙箱环境_第5张图片

第二步:查看API文档

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

支付宝公钥、私钥和沙箱环境_第6张图片

支付宝公钥、私钥和沙箱环境_第7张图片

支付宝公钥、私钥和沙箱环境_第8张图片

网关



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


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

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

第三步:签名与验签

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

支付宝公钥、私钥和沙箱环境_第11张图片

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×tamp=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×tamp=2018-08-20+23%3A23%3A12"
    data = alipay.translate_url(return_url)
    print(alipay.rsa_verify(data[0], data[1]))

你可能感兴趣的:(支付宝公钥、私钥和沙箱环境)