簽名
某些情況下(例如用戶掃碼支付成功時),支付寶會給商戶系統(tǒng)發(fā)送異步通知。在發(fā)送異步通知時,支付寶會對通知參數(shù)進行簽名,并將 “簽名字符串 sign” 作為通知參數(shù)發(fā)送給商戶系統(tǒng)。支付寶簽名的步驟是:
拼接 “待簽名字符串”;
調(diào)用簽名方法 sign();
拼接完整的請求 URL。
技術是為了解決問題而生的,進行數(shù)字簽名的目的是:
確保信息是由簽名者發(fā)送的。防止有人冒充支付寶向商戶系統(tǒng)發(fā)送異步通知;
確保信息自簽名后 到 收到為止,信息未被修改過。防止有人篡改請求參數(shù)的值。
如果商家設置的通知地址為 https://api.xx.com/receive_notify.htm,對應收到的通知示例如下:
<pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="plain" cid="n21" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 0px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"> https://api.xx.com/receive_notify.htm?
total_amount=2.00&buyer_id=2088102116773037&body=大樂透2.1&trade_no=2016071921001003030200089909&refund_fee=0.00¬ify_time=2016-07-19 14:10:49&subject=大樂透2.1&sign_type=RSA2&charset=utf-8¬ify_type=trade_status_sync&out_trade_no=0719141034-6418&gmt_close=2016-07-19 14:10:46&gmt_payment=2016-07-19 14:10:47&trade_status=TRADE_SUCCESS&version=1.0&
sign=kPbQIjX+xQc8F0/A6/AocELIjhhZnGbcBN6G4MM/HmfWL4ZiHM6fWl5NQhzXJusaklZ1LFuMo+lHQUELAYeugH8LYFvxnNajOvZhuxNFbN2LhF0l/KL8ANtj8oyPM4NN7Qft2kWJTDJUpQOzCzNnV9hDxh5AaT9FPqRS6ZKxnzM=&
gmt_create=2016-07-19 14:10:44&app_id=2015102700040153&seller_id=2088102119685838¬ify_id=4a91b7a78a503640467525113fb7d8bg8e</pre>
1 、拼接 “待簽名字符串”
支付寶對要發(fā)送的通知參數(shù)進行簽名的第一步是:拼接 “待簽名字符串”。
對需要簽名的參數(shù)進行字典排序:字典排序是按照參數(shù)的第一個字符的 ASCII 碼遞增排序(字母升序排序)。如果參數(shù)的第一個字符相同,則按照參數(shù)的第二個字符的 ASCII 碼遞增排序,以此類推。
對排序后的參數(shù)進行拼接,得到 “待簽名字符串”:將排序后的參數(shù)與其對應值,組合成
參數(shù)=參數(shù)值的格式,參數(shù)與參數(shù)之間用 & 字符連接起來,此時生成的字符串為 “待簽名字符串”。
2、調(diào)用簽名方法 sign()
支付寶對要發(fā)送的通知參數(shù)進行簽名的第二步是:調(diào)用簽名算法對應的簽名方法 sign(),生成簽名字符串 sign。
調(diào)用簽名算法對應的簽名方法時,需要提供以下幾個參數(shù):待簽名字符串 content、字符編碼方案 charset(UTF-8、GBK)和 簽名者的私鑰 privateKey。簽名方法 sign() 的處理邏輯如下:
對 “待簽名字符串” 進行編碼,得到字節(jié)數(shù)組:使用指定的字符編碼方案,將 “待簽名字符串” 編碼為字節(jié)數(shù)組(byte 類型的數(shù)組)
進行簽名、Base64 編碼,得到簽名字符串 sign:使用簽名者的私鑰 privateKey 對 “待簽名字符串” 對應的字節(jié)數(shù)組進行簽名,并對簽名結果進行 Base64 編碼,以便在網(wǎng)絡上傳輸。經(jīng)過 Base64 編碼后的字符串即為 “簽名字符串 sign”。
String sign = new String(Base64.encodeBase64(signed));
3、拼接完整的請求 URL
支付寶對要發(fā)送的通知參數(shù)進行簽名的第三步是:拼接完整的請求 URL。
拼接 sign 參數(shù):將生成的簽名字符串作為 sign 參數(shù)的 value 拼接到請求數(shù)據(jù)中。
Encode 請求數(shù)據(jù):對所有一級 key 的 value 值進行 UrlEncode 編碼。
拼接完整的請求 URL:將編碼后的請求數(shù)據(jù)發(fā)送至商戶系統(tǒng)指定的 URL。
驗簽
驗簽的全稱是:驗證指定內(nèi)容的簽名是否正確。
商戶系統(tǒng)收到支付寶發(fā)送的異步通知后,商戶系統(tǒng)需要對支付寶發(fā)送的通知參數(shù)進行驗簽處理。商戶系統(tǒng)驗簽的步驟是:
拼接 “待驗簽字符串”;
獲取簽名字符串 sign;
調(diào)用驗簽方法 verify(),獲得驗簽結果。
1、拼接 “待驗簽字符串”
對支付寶發(fā)送的通知參數(shù)進行驗簽的第一步是:拼接 “待驗簽字符串”。
獲取簽名的參數(shù):在通知返回的參數(shù)列表中,除去 sign、sign_type 兩個參數(shù)外,凡是通知返回的參數(shù)都是簽名的參數(shù)。
對簽名的參數(shù)進行 UrlDecode 解碼
對簽名的參數(shù)進行字典排序:字典排序是按照參數(shù)的第一個字符的 ASCII 碼遞增排序(字母升序排序)。如果參數(shù)的第一個字符相同,則按照參數(shù)的第二個字符的 ASCII 碼遞增排序,以此類推。
對排序后的參數(shù)進行拼接,得到 “待驗簽字符串”:將排序后的參數(shù)與其對應值,組合成
參數(shù)=參數(shù)值的格式,參數(shù)與參數(shù)之間用 & 字符連接起來,此時生成的字符串為 “待驗簽字符串”。
<pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="plain" cid="n68" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 0px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"> body=大樂透2.1&buyer_id=2088102116773037&charset=utf-8&gmt_close=2016-07-19 14:10:46&gmt_payment=2016-07-19 14:10:47¬ify_time=2016-07-19 14:10:49¬ify_type=trade_status_sync&out_trade_no=0719141034-6418&refund_fee=0.00&subject=大樂透2.1&total_amount=2.00&trade_no=2016071921001003030200089909&trade_status=TRADE_SUCCESS&version=1.0</pre>
2、取出簽名字符串 sign
對支付寶發(fā)送的通知參數(shù)進行驗簽的第二步是:從通知返回的參數(shù)列表中,取出簽名字符串 sign。
3、調(diào)用驗簽方法 verify()
對支付寶發(fā)送的通知參數(shù)進行驗簽的第三步是:調(diào)用簽名算法對應的驗簽方法 verify(),根據(jù)驗簽方法 verify() 的返回結果判定是否驗簽通過。
調(diào)用簽名算法對應的驗簽方法時,需要提供以下幾個參數(shù):待驗簽字符串 content、字符編碼方案 charset、簽名字符串 sign 和 簽名者的公鑰 publicKey。驗簽方法 verify() 的處理邏輯如下:
對 “待驗簽字符串” 進行編碼,得到字節(jié)數(shù)組:使用指定的字符編碼方案,將 “待驗簽符串” 編碼為字節(jié)數(shù)組(byte 類型的數(shù)組)
對 “簽名字符串 sign” 進行 Base64 解碼,得到字節(jié)數(shù)組:得到的這個字節(jié)數(shù)組即為當時簽名之后的初始字節(jié)數(shù)組(密文)。
-
使用公鑰驗證簽名:
boolean varifyResult = boosignature.verify(Base64.decodeBase64(sign.getBytes()))使用簽名者的公鑰 publicKey 對 密文 進行 解密,得到 “支付寶通知的摘要”;
對 “待驗簽符串” 對應的字節(jié)數(shù)組做摘要,得到摘要結果;
將 “商戶計算出的摘要結果” 和 “解密得到的摘要” 作比較:如果二者相同,則說明驗簽成功;否則說明驗簽失敗。
驗簽成功后,商戶系統(tǒng)根據(jù)通知參數(shù)進行業(yè)務處理。