-------------------
這是自己總結:
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加密,業務邏輯將會是下面這樣:
這就出問題了呀:notify_url很容易被竊取,一旦竊取后,壞蛋就可以做和商戶一樣的操作來發起支付請求,因為采用RSA加密的話壞蛋同樣可以獲取到支付寶公鑰,這樣就會一直給小明充錢。
因此,支付寶就需要確認支付請求確實是商戶發給他們的,而不是壞蛋發給他們的,這樣才能避免壞蛋惡意模擬商戶發起支付請求而給商戶造成損失。那如何才能達到這個驗證的效果呢?這就用到了數字簽名,那么我們會通過方案一的實現流程來引出數字簽名的具體概念。如果使用方案一的,我們商戶手里是需要存着商戶端私鑰和支付寶公鑰,而支付寶是需要存着商戶端公鑰和支付寶私鑰的,這樣方案一的業務流程如下:
這樣就可以確保數據交易的安全性了,我們也可以看出使用支付寶SDK確保交易安全注重的其實不是訂單信息是否加密傳輸,而是如何確保商戶端和支付寶能夠互相確認身份(形成數字簽名才是加密的真正目的,而不是加密傳輸,這里就反駁了上面的目的),我們還可以看出:
(2)數字簽名是什么?
-
數字簽名其實就是一個:原文信息加密之后得到的一個密文,然后把數字簽名拼接在要傳遞的數據后面,供接收方驗簽使用。例如上面的密文“你好,杭州”就可以用作原文“充值2元,notify_url”的數字簽名,換句話說數字簽名的生成過程其實就是一個加密過程,而數字簽名的驗簽過程就是一個解密過程。
-
數字簽名的主要目的有兩個:一、用來互相驗證接收方和發送方的身份;二、在驗證身份的基礎上再驗證一下傳遞的數據是否被篡改過。因此使用數字簽名可以用來達到數據的明文傳輸。
說到這里,我們再回想一下上面提到的RSA2加密,支付寶會分配給每個商戶單獨的一個支付寶公鑰,即支付寶為所有的商戶維護一對獨立的支付寶公私鑰,這樣的話,采用方案二也能完成數字簽名了呀,好像真得不需要我們商戶去申請商戶端公鑰和私鑰了,只不過現在需要兼容之前的RSA加密,所以依舊采用了方案一來做數字簽名。可見數字簽名並不一定非得用RSA加密來生成,MD5、SHA-1、AES等加密算法都可以用來生成數字簽名,只不過實際開發要根據實際情況來選擇適當的加密算法來生成數字簽名。通常我們會選擇RSA加密來生成數字簽名(如支付寶支付就是這樣)或者MD5加密來生成數字簽名(如微信支付就是這樣)。
(3)加密和數字簽名的聯系與區別
- 加密就是加密,而數字簽名的生成是通過加密得到的一個密文,數字簽名的驗簽就是解密。
- 加密和數字簽名的目的不一樣(上面說過了)。
- 加密和數字簽名其實是一對兄弟,可以用來共同完成數據的安全傳輸,當然也可以單獨使用。
二、支付寶支付流程解釋
由第一部分我們可以知道為了確保商戶和支付寶交易的安全性,約定采用的是給訂單信息加數字簽名傳輸的方式。
那么,支付寶也為我們提供了一鍵生成RSA密鑰的工具,可以幫助我們很快的生成一對商戶端公私鑰,一鍵生成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: 支付接口名稱
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
(2)對支付結果驗簽的簡單演示
假設我們服務端收到了來自支付寶服務端的支付結果,即:支付結果+數字簽名。
那么我們服務端就會對支付結果進行驗簽,怎么個驗法呢?
-
首先,服務端會把數字簽名截取下來,用支付寶公鑰把數字簽名給它解密,如果能解密就可以確定確實是支付寶發給我們服務端的支付結果,如果解不了就說明不是支付寶發給我們的支付結果,該報警就報警;
-
然后,如果解密成功了,服務端還需要把解密后得到的明文支付結果和支付結果來做個對比,如果一樣則說明是支付結果沒被篡改過,如果不一樣則說明支付結果中途被人篡改了,可以打110了。
三、微信支付流程與支付寶支付流程的區別
微信支付的流程大體上和支付寶是一樣的,兩者最關鍵的區別就是:
- 兩者生成數字簽名的加密算法不同:微信支付使用MD5加密算法生成數字簽名的,而支付寶支付使用RSA加密算法生成數字簽名的。(可見數字簽名就是一個加密信息,並不是說只有某種特定的加密方法才能生成數字簽名,只要是加密算法就能生成數字簽名)
- 還有一個不同是,支付寶支付,服務端會直接返回給我們調起支付寶支付的orderString,而微信支付的話,服務端會返回給我們一些信息,我們需要將這些信息拼一個請求體來調起微信支付,不過都很簡單。
那么在第二節,我們已經演示了支付寶的加驗簽大概流程,這里針對上面第一個區別,介紹一下微信支付數字簽名的生成的加驗簽流程:
-
我們服務端加簽:
- 數字簽名=MD5加密(原明文訂單信息+該應用的Api密鑰)
- 發起請求的訂單信息=原明文訂單信息后面拼接上數字簽名
-
---->傳輸---->
-
微信服務端驗簽:
- 從發起請求的訂單信息截取原明文訂單信息和數字簽名
- 用來驗簽的數字簽名=MD5加密(原明文訂單信息+該應用的Api密鑰)
- 將用來驗簽的數字簽名和數字簽名對比,如果一樣就說明是指定商戶發起的請求,而不是壞蛋模擬的。
可見,這里通過MD5加密也達到了加簽驗簽的效果,驗簽的關鍵參數就是該應用的Api密鑰,這個東西是在我們申請微信支付功能的時候,在平台上自己填寫的一個32為的字符串,因此只有我們商戶端和微信兩者知道的,這樣就用一個Api密鑰達到了類似支付寶驗簽那樣公鑰私鑰的效果。
鏈接:https://www.jianshu.com/p/6f5f0695d8fa
