目录
一、名词解释
1、什么是公钥和私钥
2、什么是加密和数字签名
二、支付宝SDK支付流程解释
1、一些关键词
2、支付宝支付流程
3、服务端对订单信息加签和对支付结果验签的简单演示
本文涉及到支付宝SDK的内容,均摘自支付宝开放平台。
因为支付宝SDK使用RSA来加密和生成数字签名,所以本文中涉及到的概念也都是针对于RSA的。
一、名词解释
1、什么是公钥和私钥
一对儿密钥生成后,会有公钥和私钥之分,我们需要把私钥保存下来,而把公钥发布出去。一对儿公钥和私钥,不能由其中一个导出另一个。
比如使用支付宝SDK的时候,我们商户端会生成一对儿密钥A和B,A是私钥,B是公钥,支付宝也会生成一对儿密钥C和D,C是私钥,D是公钥。我们商户端需要把商户端私钥A保存下来,而把商户端公钥B发布出去给支付宝,支付宝需要把支付宝私钥C保存下来,而把支付宝公钥D发布出去给我们商户端。
2、什么是加密和数字签名
加密是指我们使用一对儿密钥中的一个来对数据加密,而使用另一个来对数据解密的技术,需要注意的是公钥和私钥都可以用来加密,也都可以用来解密,并不是规定死了只能用公钥加密私钥解密,但是加解密必须是一对儿密钥之间的互相加解密,否则不能成功。
加密的目的是为了保证数据的不可读性,防止数据在传输过程中被截获。
知道了加密这个概念,我们先看一下支付宝的加密过程,再引出数字签名这个概念。接着第1小节的例子,当我们商户端和支付宝互相发布了公钥之后,我们商户端手里就有商户端私钥和支付宝公钥两个密钥,支付宝手里也有商户端公钥和支付宝私钥两个密钥。现在假设我们商户端要给支付宝传输订单信息,那么为了保证传输订单信息时数据的安全性,结合我们商户端手里所拥有的密钥,可以有两套加密方案
方案一:
(商户端)明文订单信息 + 商户端私钥加密 = 加密订单信息
(--->网络传输--->)
(支付宝)加密订单信息 + 商户端公钥解密 = 明文订单信息方案二:
(商户端)明文订单信息 + 支付宝公钥加密 = 加密订单信息
(--->网络传输--->)
(支付宝)加密订单信息 + 支付宝私钥解密 = 明文订单信息
貌似这两套加密方案都能达到对订单信息加密的效果,而且如果采用方案二,我们商户端甚至只需要存储支付宝公钥这一个密钥,都不用去申请一对儿商户端的公私钥来维护,支付宝也不用保存我们一堆商户那么多的商户端公钥了,这不是更简单吗,那为什么支付宝开放平台让我们采用的是方案一而不是方案二呢?下面来回答一下。
支付宝开放平台说明:当我们采用RSA(1024位密钥)来加密的时候,支付宝分配给所有商户的支付宝公钥都是一样的,即支付宝针对那么多的商户只负责维护一对儿支付宝公私钥,这就意味着支付宝公钥随便什么人拿到后都是一样的;而当我们采用RSA2(2048位密钥)来加密的时候,支付宝会分配给每个商户单独的一个支付宝公钥,即支付宝为每一个的商户单独的维护一对独立的支付宝公私钥,当然一个商户下的多个App的支付宝公钥是一样的。RSA是早就支持的,RSA2是最近才支持的。
知道了上面这段话,现在假设我们采用的是方案二,并且采用RSA加密(很多老业务并没有使用RSA2加密),业务逻辑将会是下面这样。
这就出问题了,RSA加密下,支付宝公钥是公开发布的,而且所有的商户用的都是同一个支付宝公钥(上面声明了RSA2加密下,支付宝才针对每个商户维护了一对儿公私钥),攻击者很容易就能获取到,而notify_url
也很容易被截获,那攻击者拿到这两个东西就可以做和商户一样的操作来发起支付请求,这样就会一直给小明充钱了。
所以支付宝就需要确认支付请求确实是商户发给他们的,而不是攻击者发给他们的。这就用到了数字签名,我们会通过方案一的实现流程来引出数字签名的具体概念。如果我们采用的是方案一,我们商户端保存的就是商户端私钥和支付宝公钥,而支付宝保存的就是需要存着商户端公钥和支付宝私钥的,业务逻辑将会是下面这样。
这样就可以保证交易的安全性了,我们也可以看出使用支付宝SDK保证交易的安全性注重的其实不是订单信息是否加密,而是如何确保商户端和支付宝能够互相确认身份,订单信息是明文的,但是后面拼接了数字签名。
数字签名其实就是明文数据加密之后得到的一个密文,只不过它是用私钥加密生成的而已,我们一般会把数字签名拼接在明文数据后面一起传递给接收方,接收方收到后用公钥解密数字签名,从而验证发送方的身份、以及明文数据是否被篡改。数字签名的生成过程其实就是一个加密过程,数字签名的验签过程就是一个解密过程。
数字签名的目的有两个:一、发送方和接收方互相验证身份;二、验证数据是否被篡改。
加密和数字签名可以单独使用,当然也可以共同使用来完成数据的安全传输。
二、支付宝SDK支付流程解释
从上面第一部分我们知道为了确保商户和支付宝交易的安全性,约定采用的是给订单信息加数字签名传输的方式。支付宝也为我们提供了一键生成RSA密钥的工具,可以帮助我们很快的生成一对商户端公私钥。以下会对支付宝SDK的支付流程做个大概的解释,并点出实际开发中我们使用支付宝SDK时应该注意的地方。
1、一些关键词
- 商户端私钥
由我们商户端自己生成的RSA私钥(必须与商户端公钥是一对),生成后要保存在服务端,绝对不能保存在客户端,也绝对不能从服务端传输给客户端。
用来对订单信息加签,加签过程一定要在服务端完成,绝对不能在客户端做加,客户端只负责用加签后的订单信息调起支付宝来支付。
- 商户端公钥
由我们商户端自己生成的RSA公钥(必须与商户端私钥是一对),生成后需要填写在支付宝开放平台。
用来给支付宝服务端验签经过我们加签后的订单信息,以确保订单信息确实是我们商户端发给支付宝的,并且确保订单信息在传输过程中未被篡改。
- 支付宝私钥
这个和我们就没关系了,支付宝私钥是他们自己生成的,也是他们自己保存的。
用来对支付结果进行加签。
- 支付宝公钥
支付宝公钥和支付宝私钥是一对,也是支付宝生成的,当我们把商户端公钥填写在支付宝开放平台后,平台就会给我们生成一个支付宝公钥,我们可以复制下来保存在服务端,同样不要保存在客户端,并且不要传输给客户端。
用来让服务端对支付宝服务端返给我们的同步或异步支付结果进行验签,以确保支付结果确实是由支付宝服务端返给我们服务端的,而且没有被篡改,对支付结果的验签工作也一定要在服务端完成。
2、支付宝SDK的支付流程
第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、服务端对订单信息加签和对支付结果验签的简单演示
上面已经说过了:订单信息的加签和支付结果的验签是一定要在服务端做的,绝对不能在客户端做。
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×tamp=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×tamp=2016-07-28 20:36:11&version=1.0&sign=GsSZgPloF1vn52XAItRAldwQAbzIgkDyByCxMfTZG%2FMapRoyrNIJo4U1LUGjHp6gdBZ7U8jA1kljLPqkeGv8MZigd3kH25V0UK3Jc3C94Ngxm5S%2Fz5QsNr6wnqNY9sx%2Bw6DqNdEQnnks7PKvvU0zgsynip50lAhJmflmfHvp%2Bgk%3D
3.2 对支付结果验签的简单演示
假设我们服务端收到了来自支付宝服务端的支付结果,即:支付结果+数字签名。
那么我们服务端就会对支付结果进行验签,怎么个验法呢?
首先,服务端会把数字签名截取下来,用支付宝公钥把数字签名给它解密,如果能解密就可以确定确实是支付宝发给我们服务端的支付结果,如果解不了就说明不是支付宝发给我们的支付结果,该报警就报警;
然后,如果解密成功了,服务端还需要把解密后得到的明文支付结果和支付结果来做个对比,如果一样则说明是支付结果没被篡改过,如果不一样则说明支付结果中途被人篡改了,可以打110了。