从⽀付宝SDK的⽀付流程理解什么是公钥和私钥,什么是加密和数字签名
-------------------
这是⾃⼰总结:
⽀付宝SDK⽀付⽤到的公钥与私钥整理如下:
1.商户应⽤公钥
2.商户应⽤私钥
3.⽀付宝公钥
4.⽀付宝私钥
商户应⽤的公钥与私钥⽣成⽅式:
上传应⽤公钥并获取⽀付宝公钥:
调⽤⽀付宝sdk的时候参数提⽰很清楚:⼀个是商户应⽤的私钥,⼀个是⽀付宝公钥。⼀定要理清楚
warning:涉及到⽀付宝SDK的内容,均摘⾃⽀付宝开放平台。
warning:同时因为⽀付宝SDK使⽤的是RSA加密算法来加密和⽣成数字签名,因此本⽂中所涉及到的概念也都是针对于RSA⾮对称加密算法。所以可以和后⾯第四篇结合起来看。
⽬录
⼀、名词解释
 1、什么是公钥和私钥?
 2、什么是加密和数字签名,加密和数字签名的联系与区别
(注意,数字签名其实是独⽴于哈希算法、AES对称加密、RSA⾮对称加密的,或者说数字签名是它们三者的⼀种应⽤,不要以为数字签名就是专属于RSA⾮对称加密的。)
⼆、⽀付宝⽀付流程解释
 1、⼀些关键词
 2、⽀付宝⽀付流程
 3、服务端对订单信息加签和对⽀付结果验签的简单演⽰
三、⽀付流程与⽀付宝⽀付流程的区别
⼀、名词解释
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加密,业务逻辑将会是下⾯这样:
⽅案⼆:订单信息加密传输,并且是采⽤RSA加密
这就出问题了呀:notify_url很容易被窃取,⼀旦窃取后,坏蛋就可以做和商户⼀样的操作来发起⽀付请求,因为采⽤RSA加密的话坏蛋同样可以获取到⽀付宝公钥,这样就会⼀直给⼩明充钱。
因此,⽀付宝就需要确认⽀付请求确实是商户发给他们的,⽽不是坏蛋发给他们的,这样才能避免坏蛋恶意模拟商户发起⽀付请求⽽给商户造成损失。那如何才能达到这个验证的效果呢?这就⽤到了数字签名,那么我们会通过⽅案⼀的实现流程来引出数字签名的具体概念。如果使⽤⽅案⼀的,我们商户⼿⾥是需要存着商户端私钥和⽀付宝公钥,⽽⽀付宝是需要存着商户端公钥和⽀付宝私钥的,这样⽅案⼀的业务流程如下:
⽅案⼀:对订单信息添加数字签名(简称加签)来传输
这样就可以确保数据交易的安全性了,我们也可以看出使⽤⽀付宝SDK确保交易安全注重的其实不是订单信息是否加密传输,⽽是如何确保商户端和⽀付宝能够互相确认⾝份(形成数字签名才是加密的真正⽬的,⽽不是加密传输,这⾥就反驳了上⾯的⽬的),我们还可以看出:
(2)数字签名是什么?
数字签名其实就是⼀个:原⽂信息加密之后得到的⼀个密⽂,然后把数字签名拼接在要传递的数据后⾯,供接收⽅验签使⽤。例如上⾯的密⽂“你好,杭州”就可以⽤作原⽂“充值2元,notify_url”的数字签名,换句话说数字签名的⽣成过程其实就是⼀个加密过程,⽽数字签名的验签过程就是⼀个解密过程。
数字签名的主要⽬的有两个:⼀、⽤来互相验证接收⽅和发送⽅的⾝份;⼆、在验证⾝份的基础上再验证⼀下传递的数据是否被篡改过。因此使⽤数字签名可以⽤来达到数据的明⽂传输。
说到这⾥,我们再回想⼀下上⾯提到的RSA2加密,⽀付宝会分配给每个商户单独的⼀个⽀付宝公钥,即⽀付宝为所有的商户维护⼀对独⽴的⽀付宝公私钥,这样的话,采⽤⽅案⼆也能完成数字签名了呀,好像真得不需要我们商户去申请商户端公钥和私钥了,只不过现在需要兼容之前的RSA加密,所以依旧采⽤了⽅案⼀来做数字签名。可见数字签名并不⼀定⾮得⽤RSA加密来⽣成,MD5、SHA-1、AES等加密算法都可以⽤来⽣成数字签名,只不过实际开发要根据实际情况来选择适当的加密算法来⽣成数字签名。通常我们会选择RSA加密来⽣成数字签名(如⽀付宝⽀付就是这样)或者MD5加密来⽣成数字签名
(如⽀付就是这样)。
(3)加密和数字签名的联系与区别
加密就是加密,⽽数字签名的⽣成是通过加密得到的⼀个密⽂,数字签名的验签就是解密。
加密和数字签名的⽬的不⼀样(上⾯说过了)。
加密和数字签名其实是⼀对兄弟,可以⽤来共同完成数据的安全传输,当然也可以单独使⽤。
⼆、⽀付宝⽀付流程解释
由第⼀部分我们可以知道为了确保商户和⽀付宝交易的安全性,约定采⽤的是给订单信息加数字签名传输的⽅式。
那么,⽀付宝也为我们提供了⼀键⽣成RSA密钥的⼯具,可以帮助我们很快的⽣成⼀对商户端公私钥,
以下会对⽀付宝的⽀付流程做个⼤概的解释,并点出实际开发中我们使⽤⽀付宝SDK时应该注意的地⽅:
1、⼀些关键词
商户端私钥:
由我们⾃⼰⽣成的RSA私钥(必须与商户端公钥是⼀对),⽣成后要保存在服务端,绝对不能保存在客户端,也绝对不能从服务端下发
⽤来对订单信息进⾏加签,加签过程⼀定要在服务端完成,绝对不能在客户端做加签⼯作,客户端只负责⽤加签后的订单信息调起⽀付宝来⽀付商户端公钥:
由我们⾃⼰⽣成的RSA公钥(必须与商户端私钥是⼀对),⽣成后需要填写在⽀付宝开放平台
⽤来给⽀付宝服务端验签经过我们加签后的订单信息,以确保订单信息确实是我们商户端发给⽀付宝的,并且确保订单信息在传输过程中未被篡改(下⾯会举例⼦)⽀付宝私钥:
这个和我们就没关系了,⽀付宝私钥是⼈家⾃⼰⽣成的,他们⾃⼰保存的
⽤来对⽀付结果进⾏加签
⽀付宝公钥:
⽀付宝公钥和⽀付宝私钥是⼀对,也是⽀付宝⽣成的,当我们把商户端公钥填写在⽀付宝开放平台后,
平台就会给我们⽣成⼀个⽀付宝公钥,我们可以复制下来保存在服务端,同样不要保存在客户端,并且不要下发,避免被反编译或截获,⽽被篡改⽀付结果
⽤来让服务端对⽀付宝服务端返给我们的同步或异步⽀付结果进⾏验签,以确保⽀付结果确实是由⽀付宝服务端返给我们服务端的,⽽且没有被篡改,对⽀付结果的验签⼯作也⼀定要在服务端完成,绝对不能在客户端验签,因为⽀付宝公钥⼀旦存储在客户端⽤来验签,那就可能被反编译,这样就谁都可以验签⽀付结果并篡改了
2、⽀付宝⽀付流程
⽀付宝⽀付流程时序图
第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: ⽀付接⼝名称
// NOTE: 参数编码格式
order.charset = @"utf-8";
// NOTE: 当前时间点
NSDateFormatter* formatter = [NSDateFormatter new];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
api接口和sdk接口的区别
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.al_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&ade.app.pay&sign_type=RSA2×tamp=2016-07-28 20:36:11&version=1.0
第3步:调⽤⽀付宝SDK提供的API,⽤客户端私钥对第2步⽣成的订单信息字符串进⾏加密⽣成数字签名,⽣成的数字签名类似于下⾯酱式⼉:
GsSZgPloF1vn52XAItRAldwQAbzIgkDyByCxMfTZG%2FMapRoyrNIJo4U1LUGjHp6gdBZ7U8jA1kljLPqkeGv8MZigd3kH25V0UK3Jc3C94Ngxm5S%2Fz5QsNr6wnqNY9sx%2Bw6DqNdEQnnks
第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&ade.app.pay&sign_type=RSA2×tamp=2016-07-28
20:36:11&version=1.0&sign=GsSZgPloF1vn52XAItRAldwQAbzIgkDyByCxMfTZG%2FMapRoyrNIJo4U1LUGjHp6gdBZ7U8jA1kljLPqkeGv8MZigd3kH25V0UK3Jc3C94Ngxm5S%2Fz5QsNr6wnq (2)对⽀付结果验签的简单演⽰
假设我们服务端收到了来⾃⽀付宝服务端的⽀付结果,即:⽀付结果+数字签名。
那么我们服务端就会对⽀付结果进⾏验签,怎么个验法呢?
⾸先,服务端会把数字签名截取下来,⽤⽀付宝公钥把数字签名给它解密,如果能解密就可以确定确实是⽀付宝发给我们服务端的⽀付结果,如果解不了就说明不是⽀付宝
发给我们的⽀付结果,该报警就报警;
然后,如果解密成功了,服务端还需要把解密后得到的明⽂⽀付结果和⽀付结果来做个对⽐,如果⼀样则说明是⽀付结果没被篡改过,如果不⼀样则说明⽀付结果中途被⼈
篡改了,可以打110了。
三、⽀付流程与⽀付宝⽀付流程的区别
⽀付的流程⼤体上和⽀付宝是⼀样的,两者最关键的区别就是:
两者⽣成数字签名的加密算法不同:⽀付使⽤MD5加密算法⽣成数字签名的,⽽⽀付宝⽀付使⽤RSA加密算法⽣成数字签名的。(可见数字签名就是⼀个加密信息,
并不是说只有某种特定的加密⽅法才能⽣成数字签名,只要是加密算法就能⽣成数字签名)
还有⼀个不同是,⽀付宝⽀付,服务端会直接返回给我们调起⽀付宝⽀付的orderString,⽽⽀付的话,服务端会返回给我们⼀些信息,我们需要将这些信息拼⼀个请求
体来调起⽀付,不过都很简单。
那么在第⼆节,我们已经演⽰了⽀付宝的加验签⼤概流程,这⾥针对上⾯第⼀个区别,介绍⼀下⽀付数字签名的⽣成的加验签流程:
我们服务端加签:
数字签名=MD5加密(原明⽂订单信息+该应⽤的Api密钥)
发起请求的订单信息=原明⽂订单信息后⾯拼接上数字签名
---->传输---->
服务端验签:
从发起请求的订单信息截取原明⽂订单信息和数字签名
⽤来验签的数字签名=MD5加密(原明⽂订单信息+该应⽤的Api密钥)
将⽤来验签的数字签名和数字签名对⽐,如果⼀样就说明是指定商户发起的请求,⽽不是坏蛋模拟的。
可见,这⾥通过MD5加密也达到了加签验签的效果,验签的关键参数就是该应⽤的Api密钥,这个东西是在我们申请⽀付功能的时候,在平台上⾃⼰填写的⼀个32为的字符串,因此只有我们商户端和两者知道的,这样就⽤⼀个Api密钥达到了类似⽀付宝验签那样公钥私钥的效果。