原文链接:https://www.jianshu.com/p/6f5f0695d8fa

warning:涉及到支付宝SDK的内容,均摘自支付宝开放平台。

warning:同时因为支付宝SDK使用的是RSA加密算法来加密和生成数字签名,因此本文中所涉及到的概念也都是针对于RSA非对称加密算法。

一、名词解释

1、什么是公钥和私钥?

首先要明白公钥和私钥只是一个相对概念,就是说我们不能单纯的去称呼一对密钥中的一个为公钥,另一个为私钥,它们的公私性总是相对于生成者来说的。一对密钥生成后,保存在生成者手里的就是生成者私钥,生成者发布出去的就是生成者公钥,可以看到我们在称呼它们的时候前面带上了生成者,这样可以便于我们理解,避免混淆概念。一对儿公私钥,不能由其中的一个导出另一个。

可以暂时这么理解:
一对密钥在刚生成的时候是没有公私之分的,但是生成者会保留一个在自己手里,发布一个给别人用,正是这个“保留与发布”的操作才使得这对密钥有了公私之分,那么对于生成者来说,保留在自己手里的密钥就被称作生成者私钥,发布给别人用的那个密钥就被称作生成者公钥,注意这里的称呼带上了生成者,就是为了表明一对密钥的公私性总是相对于它们的生成者来说的。(实际中私钥和公钥在生成的时候已经具备了公私性,因为公钥和私钥是不同的生成机理,但这样理解也是没有错的,有助于帮助我们理清后面的关系)

比如:
我们使用支付宝SDK的时候,我们商户端会生成一对密钥A和B,支付宝也会生成一对密钥C和D。
那么如果我们商户端保存了A,而把B发布给了支付宝,A就被称作商户端私钥,B就被称作商户端公钥。(注意称呼公私钥的时候带上生成者的名字,这样可以便于我们理解,避免混淆概念)
当然我们也可以保存B,而把A发布给支付宝,这样B就被称作商户端私钥,A就被称作商户端公钥。(实际中不会这么做,因为公私钥已经提前确定好了,它们的生成机理不同但这样理解也是没有错的,有助于帮助我们理清后面的关系)
同样,假设支付宝保存了C,而把D发布给了我们,那么C就被称作支付宝私钥,D就被称作支付宝公钥,反之同理。

2、什么是加密和数字签名,加密和数字签名的联系与区别

(1)什么是加密?

  • 加密是指:我们使用一对公私钥中的一个密钥来对数据进行加密,而使用另一个密钥来进行解密的技术,需要注意的是公钥和私钥都可以用来加密,也都可以用来解密,并不是说规定死了只能私钥来加密公钥来解密,或者公钥来加密私钥来解密,但这个加解密必须是一对密钥之间的互相加解密,否则不能成功。
  • 加密的目的是:为了确保数据传输过程中的不可读性,就是不想让别人看到嘛。

接下来,我们就着知道了加密这个概念,先看一下支付宝的加密过程,再引出数字签名这个概念。

接着第1小节的比如,继续:
当我们商户端和支付宝互相发布了公钥之后,我们商户端手里就有商户端私钥和支付宝公钥两个密钥,支付宝手里也有商户端公钥和支付宝私钥两个密钥。现在假设我们商户端要给支付宝传递订单信息,那么为了保证传递订单信息时数据的安全性(注意这个加密的目的,听起来好像很合理,但其实我们商户端给支付宝传递订单信息是明文传输的,只不过有数字签名来保证数据没被篡改而已,后面会说到,现在先按着这个错误的目的往下看,自然引出下面的反驳),针对我们商户端手里拥有的密钥,可以有两套加密方案,分别是:

  • 方案一:
    (商户端)明文订单信息+商户端私钥加密=加密订单信息(---->传递---->)(支付宝)加密订单信息+商户端公钥解密=明文订单信息
  • 方案二:
    (商户端)明文订单信息+支付宝公钥加密=加密订单信息(---->传递---->)(支付宝)加密订单信息+支付宝私钥解密=明文订单信息

貌似两种加密方案都能达到对订单信息加密传输的效果,那为什么我们看到支付宝开发平台让我们采用的是方案一而不是方案二呢?细想一下,采用方案二,我们商户端甚至只需要存储支付宝公钥这一个密钥,都不用我们去申请一对商户端的公私钥来维护,支付宝也不用保存我们一堆商户那么多的商户端公钥了呀,这不是更简单吗,那真得为什么支付宝不让我们用方案二呢?下面来回答一下:

首先,支付宝开放平台说明:当我们采用RSA(1024位密钥)来加密的时候,支付宝分配给所有商户的支付宝公钥都是一样的,即支付宝针对那么多的商户只负责维护一对支付宝公私钥;而当我们采用RSA2(2048位密钥)来加密的时候,支付宝会分配给每个商户单独的一个支付宝公钥,即支付宝为每一个的商户单独的维护一对独立的支付宝公私钥,当然一个商户下的多个App的支付宝公钥是一样的。RSA是早就支持的,RSA2是最近才支持的。见此篇

好,知道了上面这段话,那现在假设我们使用的是方案二(这样我们商户端就省去商户端申请商户端公私钥的这一过程,而只保留支付宝公钥),并且采用RSA加密,业务逻辑将会是下面这样:

20190723180244573.png

这就出问题了呀:notify_url很容易被窃取,一旦窃取后,坏蛋就可以做和商户一样的操作来发起支付请求,因为采用RSA加密的话坏蛋同样可以获取到支付宝公钥,这样就会一直给小明充钱。

因此,支付宝就需要确认支付请求确实是商户发给他们的,而不是坏蛋发给他们的,这样才能避免坏蛋恶意模拟商户发起支付请求而给商户造成损失。那如何才能达到这个验证的效果呢?这就用到了数字签名,那么我们会通过方案一的实现流程来引出数字签名的具体概念。如果使用方案一的,我们商户手里是需要存着商户端私钥和支付宝公钥,而支付宝是需要存着商户端公钥和支付宝私钥的,这样方案一的业务流程如下:

20190723180359639.png

这样就可以确保数据交易的安全性了,我们也可以看出使用支付宝SDK确保交易安全注重的其实不是订单信息是否加密传输,而是如何确保商户端和支付宝能够互相确认身份(形成数字签名才是加密的真正目的,而不是加密传输,这里就反驳了上面的目的)

(2)数字签名是什么?

  • 数字签名其实就是一个:原文信息加密之后得到的一个密文,然后把数字签名拼接在要传递的数据后面,供接收方验签使用。例如上面的密文“你好,杭州”就可以用作原文“充值2元,notify_url”的数字签名,换句话说数字签名的生成过程其实就是一个加密过程,而数字签名的验签过程就是一个解密过程。
  • 数字签名的主要目的有两个:一、用来互相验证接收方和发送方的身份;二、在验证身份的基础上再验证一下传递的数据是否被篡改过。因此使用数字签名可以用来达到数据的明文传输。

说到这里,我们再回想一下上面提到的RSA2加密,支付宝会分配给每个商户单独的一个支付宝公钥,即支付宝为所有的商户维护一对独立的支付宝公私钥,这样的话,采用方案二也能完成数字签名了呀,好像真得不需要我们商户去申请商户端公钥和私钥了,只不过现在需要兼容之前的RSA加密,所以依旧采用了方案一来做数字签名。可见数字签名并不一定非得用RSA加密来生成,MD5、SHA-1、AES等加密算法都可以用来生成数字签名,只不过实际开发要根据实际情况来选择适当的加密算法来生成数字签名。通常我们会选择RSA加密来生成数字签名(如支付宝支付就是这样)或者MD5加密来生成数字签名(如微信支付就是这样)。

(3)加密和数字签名的联系与区别

  • 加密就是加密,而数字签名的生成是通过加密得到的一个密文,数字签名的验签就是解密。
  • 加密和数字签名的目的不一样(上面说过了)。
  • 加密和数字签名其实是一对兄弟,可以用来共同完成数据的安全传输,当然也可以单独使用。

二、支付宝支付流程解释

由第一部分我们可以知道为了确保商户和支付宝交易的安全性,约定采用的是给订单信息加数字签名传输的方式。

那么,支付宝也为我们提供了一键生成RSA密钥的工具,可以帮助我们很快的生成一对商户端公私钥,一键生成RSA密钥工具的下载地址

以下会对支付宝的支付流程做个大概的解释,并点出实际开发中我们使用支付宝SDK时应该注意的地方:

1、一些关键词

商户端私钥:

  • 由我们自己生成的RSA私钥(必须与商户端公钥是一对),生成后要保存在服务端,绝对不能保存在客户端,也绝对不能从服务端下发
  • 用来对订单信息进行加签,加签过程一定要在服务端完成,绝对不能在客户端做加签工作,客户端只负责用加签后的订单信息调起支付宝来支付

商户端公钥:

  • 由我们自己生成的RSA公钥(必须与商户端私钥是一对),生成后需要填写在支付宝开放平台
  • 用来给支付宝服务端验签经过我们加签后的订单信息,以确保订单信息确实是我们商户端发给支付宝的,并且确保订单信息在传输过程中未被篡改(下面会举例子)

支付宝私钥:

  • 这个和我们就没关系了,支付宝私钥是人家自己生成的,他们自己保存的
  • 用来对支付结果进行加签

支付宝公钥:

  • 支付宝公钥和支付宝私钥是一对,也是支付宝生成的,当我们把商户端公钥填写在支付宝开放平台后,平台就会给我们生成一个支付宝公钥,我们可以复制下来保存在服务端,同样不要保存在客户端,并且不要下发,避免被反编译或截获,而被篡改支付结果
  • 用来让服务端对支付宝服务端返给我们的同步或异步支付结果进行验签,以确保支付结果确实是由支付宝服务端返给我们服务端的,而且没有被篡改,对支付结果的验签工作也一定要在服务端完成,绝对不能在客户端验签,因为支付宝公钥一旦存储在客户端用来验签,那就可能被反编译,这样就谁都可以验签支付结果并篡改了

2、支付宝支付流程

aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xMTE2NzI1LTQwZWIxMGUyYzFhNWIyMmEucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy8xMDAw.jpg

支付宝支付流程时序图

  • 第1步:用户在我们客户端里选择好订单信息后(比如要充值11块钱),然后选择支付宝支付;
  • 第2步:我们客户端会走个接口告诉服务端用户是选择了哪个订单信息,服务端收到请求后,就会使用商户端私钥对这个订单信息进行加密生成数字签名,然后把这个数字签名拼接在明文订单信息后,形成一个加签订单信息orderString;(下面会用客户端的代码来简单演示一下这个加签的过程,让我们看到订单信息是如何通过加签最终转变为调起支付宝那个orderString的)
  • 第3步:服务端通过第2步那个接口把orderString返回给客户端;
  • 第4步、第5步:客户端使用这个orderString调用一下支付宝SDK的提供的支付API发起支付就可以调起支付宝客户端来支付了,与此同时当然支付宝服务端也收到了这个支付请求;
  • 第6步:支付宝服务端收到这个orderString后就会使用我们填写在支付宝开放平台的那个商户端公钥对这个加签订单信息进行验签,并处理交易来完成支付,然后就会得到一个交易结果(交易成功啊、失败啊、中途取消啊等等),支付宝服务端又会使用支付宝私钥对这个交易结果进行加签;(支付宝服务端的加签、验签操作和我们服务端做的加签、验签操作是类似的)
  • 第7、8、9、10步: (实际开发中会忽略掉9、10步,这里只是说明它们在干什么)支付宝服务端会把这个加签后的交易结果同步的返回给我们客户端,然后我们客户端需要把这个加签交易结果返回给服务端去验签(注意千万不能在客户端验签,倒不是说不能在客户端验签,主要的意思是说不要把支付宝公钥存在客户端),服务端验签结束后把真正的支付结果返回给我们客户端,客户端根据这个支付结果作出相应的界面处理。但是,支付宝开放平台说了:

    1、强烈建议我们开发者直接依赖支付宝服务端返回给我们服务端的异步支付结果(即第12步),而忽略同步支付结果,因为这个同步支付结果有可能收不到啊,比如我们App在调用支付宝支付的过程中突然闪退了,那这个同步支付结果我们App是收不到的,自然也就无法传给服务端去验签,那不就完犊子了嘛,但是异步支付结果支付宝服务端是肯定能确保返回给我们的服务端的;
    2、但是同步的支付结果和异步的支付结果都可以作为支付完成的凭证,所以为了简化集成流程,我们客户端就可以直接将同步支付结果作为支付结束的一个凭证,在忽略掉第7、8、9、10步的基础上,直接根据同步支付结果的状态码来作出相应的界面处理,甚至都不去关心支付结果的具体信息,而真正改变用户金钱字段的业务操作则由服务端根据异步的支付信息经过验签后去做改变,当然如果服务端验签失败了就说明支付结果被篡改了,是要报警的^_。
    
  • 第11步:客户端直接将同步支付结果作为支付结束的一个凭证,根据状态码来作出相应的界面处理;
  • 第12步、第13步:在第6步结束后,支付宝服务端会异步的把加签支付结果返回给我们服务端,服务端用支付宝公钥对这个支付结果进行验签,验签后根据支付结果作出实际的业务处理(如支付成功则给用户的金钱字段加上11,如果失败则不做处理等等);第13步是服务端会把实际的业务处理完毕后还需要在他们服务端SDK的回调里把业务的处理结果返回给支付宝服务端(如实际业务处理成功就返回给支付宝服务端success就算结束本次交易,如实际业务处理失败就返回给支付宝服务端fail,支付宝服务端就去再次调用服务端SDK来重新处理实际的业务逻辑直到成功,如果超过了一定的次数,服务端还是给支付宝服务端返回fail,说明是我们的系统出了问题,支付宝服务端就不会再触发服务端SDK来重新处理实际的业务了,这种情况下咱们的客户就会打我们的客服了,说我的支付宝都扣钱了,但为什么没充值成功呢,我们就人工的给人家处理一下)。

3、服务端对订单信息加签和对支付结果验签的简单演示

上面已经说过了:订单信息的加签和支付结果的验签是一定要在服务端做的,绝对不能在客户端做。

(1)服务端对订单信息加签的简单演示

下面是在客户端对订单信息加签的过程,仅仅是为了模拟服务端来表明订单信息是如何通过加签最终转变为orderString的,千万不要觉得订单信息的加签过程也可以放在客户端完成。

  • 第1步:用AppID、签名类型、商品标题、商品描述、商品价格啊等一些信息,构建一个支付宝SDK提供的订单信息类的实体,如:
//将商品信息赋予AlixPayOrder的成员变量
Order* order = [Order new];
// NOTE: app_id设置
order.app_id = appID;
// NOTE: 支付接口名称
order.method = @"alipay.trade.app.pay";
// NOTE: 参数编码格式
order.charset = @"utf-8";
// NOTE: 当前时间点
NSDateFormatter* formatter = [NSDateFormatter new];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
order.timestamp = [formatter stringFromDate:[NSDate date]];
// NOTE: 支付版本
order.version = @"1.0";
// NOTE: sign_type设置
order.sign_type = @"RSA";
// NOTE: 商品数据
order.biz_content = [BizContent new];
order.biz_content.body = @"我是测试数据";
order.biz_content.subject = @"1";
order.biz_content.out_trade_no = [self generateTradeNO]; //订单ID(由商家自行制定)
order.biz_content.timeout_express = @"30m"; //超时时间设置
order.biz_content.total_amount = [NSString stringWithFormat:@"%.2f", 0.01]; //商品价格
  • 第2步:使用支付宝SDK提供的API,把第一步构建的订单信息实体转换成一个订单信息字符串,转换后会是一个类似下面这样的字符串:
app_id=2015052600090779&biz_content={"timeout_express":"30m","seller_id":"",
"product_code":"QUICK_MSECURITY_PAY","total_amount":"0.02","subject":"1",
"body":"我是测试数据","out_trade_no":"ZQLM3O56MJD4SK3"}&charset=utf-8
&method=alipay.trade.app.pay&sign_type=RSA2
&timestamp=2016-07-28 20:36:11&version=1.0
  • 第3步:调用支付宝SDK提供的API,用客户端私钥对第2步生成的订单信息字符串进行加密生成数字签名,生成的数字签名类似于下面酱式儿:
GsSZgPloF1vn52XAItRAldwQAbzIgkDyByCxMfTZG%2FMapRoyrNIJo4U1LUGjHp6gdBZ7U8jA1kljLPqkeGv8MZigd3kH25V0UK3Jc3C94Ngxm5S%2Fz5QsNr6wnqNY9sx%2Bw6DqNdEQnnks7PKvvU0zgsynip50lAhJmflmfHvp%2Bgk%3D
  • 第4步:按照支付宝规定的格式要求,把数字签名拼接在原明文订单信息字符串的后面就形成了最终能够调起支付宝支付的那个加签订单信息--orderString,类似于下面这样:
app_id=2015052600090779&biz_content={"timeout_express":"30m","seller_id":"",
"product_code":"QUICK_MSECURITY_PAY","total_amount":"0.02","subject":"1",
"body":"我是测试数据","out_trade_no":"ZQLM3O56MJD4SK3"}&charset=utf-8
&method=alipay.trade.app.pay&sign_type=RSA2&timestamp=2016-07-28 20:36:11&version=1.0
&sign=GsSZgPloF1vn52XAItRAldwQAbzIgkDyByCxMfTZG%2FMapRoyrNIJo4U1LUGjHp6gdBZ7U8jA1kljLPqkeGv8MZigd3kH25V0UK3Jc3C94Ngxm5S%2Fz5QsNr6wnqNY9sx%2Bw6DqNdEQnnks7PKvvU0zgsynip50lAhJmflmfHvp%2Bgk%3D

(2)对支付结果验签的简单演示

假设我们服务端收到了来自支付宝服务端的支付结果,即:支付结果+数字签名。

那么我们服务端就会对支付结果进行验签,怎么个验法呢?

  • 首先,服务端会把数字签名截取下来,用支付宝公钥把数字签名给它解密,如果能解密就可以确定确实是支付宝发给我们服务端的支付结果,如果解不了就说明不是支付宝发给我们的支付结果,该报警就报警;
  • 然后,如果解密成功了,服务端还需要把解密后得到的明文支付结果和支付结果来做个对比,如果一样则说明是支付结果没被篡改过,如果不一样则说明支付结果中途被人篡改了,可以打110了。

三、微信支付流程与支付宝支付流程的区别

微信支付的流程大体上和支付宝是一样的,两者最关键的区别就是:

  • 两者生成数字签名的加密算法不同:微信支付使用MD5加密算法生成数字签名的,而支付宝支付使用RSA加密算法生成数字签名的。(可见数字签名就是一个加密信息,并不是说只有某种特定的加密方法才能生成数字签名,只要是加密算法就能生成数字签名)
  • 还有一个不同是,支付宝支付,服务端会直接返回给我们调起支付宝支付的orderString,而微信支付的话,服务端会返回给我们一些信息,我们需要将这些信息拼一个请求体来调起微信支付,不过都很简单。

那么在第二节,我们已经演示了支付宝的加验签大概流程,这里针对上面第一个区别,介绍一下微信支付数字签名的生成的加验签流程:

  • 我们服务端加签:

    数字签名=MD5加密(原明文订单信息+该应用的Api密钥)
    发起请求的订单信息=原明文订单信息后面拼接上数字签名
    
  • ---->传输---->
  • 微信服务端验签:

    从发起请求的订单信息截取原明文订单信息和数字签名
    用来验签的数字签名=MD5加密(原明文订单信息+该应用的Api密钥)
    将用来验签的数字签名和数字签名对比,如果一样就说明是指定商户发起的请求,而不是坏蛋模拟的。
    

可见,这里通过MD5加密也达到了加签验签的效果,验签的关键参数就是该应用的Api密钥,这个东西是在我们申请微信支付功能的时候,在平台上自己填写的一个32为的字符串,因此只有我们商户端和微信两者知道的,这样就用一个Api密钥达到了类似支付宝验签那样公钥私钥的效果。


版权声明:文章转载请注明来源,如有侵权请联系博主删除!
最后修改:2019 年 12 月 25 日 11 : 54 AM
如果觉得我的文章对你有用,请随意赞赏