


PHP、Java、C#實現(xiàn)URI參數(shù)簽名算法,保準應用與REST服務器之間的安全通信,防止Secret Key盜用、數(shù)據(jù)篡改等惡意攻擊行為
Jun 13, 2016 pm 12:10 PM
PHP、Java、C#實現(xiàn)URI參數(shù)簽名算法,確保應用與REST服務器之間的安全通信,防止Secret Key盜用、數(shù)據(jù)篡改等惡意攻擊行為
簡介
應用基于HTTP POST或HTTP GET請求發(fā)送Open API調(diào)用請求時,為了確保應用與REST服務器之間的安全通信,防止Secret Key盜用、數(shù)據(jù)篡改等惡意攻擊行為,REST服務器使用了參數(shù)簽名機制。應用在調(diào)用Open API之前,需要為其所有請求參數(shù)計算一個MD5簽名,并追加到請求參數(shù)中,參數(shù)名為“sign”。REST服務器在接收到請求時會重新計算簽名,并判斷其值是否與應用傳遞過來的sign參數(shù)值一致,以此判定當前Open API調(diào)用請求是否是被第三者偽造或篡改。
應用在調(diào)用Open API之前需要通過?OAuth2.0服務獲得用戶或平臺的授權(quán),獲取到授權(quán)后將會拿到以下3個重要參數(shù):
- access_token:基于https調(diào)用Open API時所需要的訪問授權(quán)碼;
- session_key:基于http調(diào)用Open API時所需要的訪問授權(quán)碼;
- session_secret:基于http調(diào)用Open API時計算參數(shù)簽名用的簽名密鑰。
其中,session_secret這個參數(shù)就是做參數(shù)簽名時所需要的簽名密鑰。這與Facebook、人人網(wǎng)等平臺稍微有所區(qū)別,這兩個平臺在做參數(shù)簽名時所用的簽名密鑰一般有2個:
- 如果是通過應用服務端調(diào)用Open API,則注冊應用時所拿到的應用密鑰(即API Key)就是參數(shù)簽名密鑰;
- 如果是通過JavaScript、ActionScript等客戶端語言調(diào)用Open API,則應用獲取到用戶授權(quán)后所拿到的Session Secret就是參數(shù)簽名密鑰。當然,通過服務端調(diào)用Open API時也可以用Session Secret作為簽名密鑰。
簽名算法
假設參與參數(shù)簽名計算的請求參數(shù)分別是“k1”、“k2”、“k3”,它們的值分別是“v1”、“v2”、“v3”,則參數(shù)簽名計算方法如下:
- 將請求參數(shù)格式化為“key=value”格式,即“k1=v1”、“k2=v2”、“k3=v3”;
- 將格式化好的參數(shù)鍵值對以字典序升序排列后,拼接在一起,即“k1=v1k2=v2k3=v3”;
- 在拼接好的字符串末尾追加上應用通過OAuth2.0協(xié)議獲取Access Token時所獲取到的session_secret參數(shù)值;
- 上述字符串的MD5值即為簽名的值。
注意:計算簽名時的請求參數(shù)中不要包含sign(簽名)參數(shù),因為sign參數(shù)的值此時還不知道,有待計算。
另外,計算簽名的時候不需要對參數(shù)進行urlencode處理(“application/x-www-form-urlencoded”編碼),但是發(fā)送請求的時候需要進行urlencode處理,這是很多開發(fā)者最容易犯錯的地方。
簽名過程示例
假設某個應用需要獲取某個uid為67411167的用戶的基本資料,應用在之前的通過OAuth2.0服務獲取Access Token的過程中所拿到的session_key和session_secret參數(shù)值分別為:
- session_key:?"9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A="
- session_secret:?"27e1be4fdcaa83d7f61c489994ff6ed6"
調(diào)用Open API時的系統(tǒng)時間(PHP中可以通過date('Y-m-d H:i:s')來獲取當前系統(tǒng)時間)為"2011-06-21 17:18:09",希望REST服務器以JSON格式返回調(diào)用結(jié)果,即相當于參與參數(shù)簽名計算的請求參數(shù)集合為:
<span style="color: #000000;">[ </span>"session_key" => "9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A="<span style="color: #000000;">, </span>"timestamp" => "2011-06-21 17:18:09"<span style="color: #000000;">, </span>"format" => "json"<span style="color: #000000;">, </span>"uid" => 67411167<span style="color: #000000;">]</span>
則計算簽名的具體過程如下:
- 將請求參數(shù)格式化為“key=value”格式,格式化后的請求參數(shù)集合為:
<span style="color: #000000;"> [ </span>"session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A="<span style="color: #000000;">, </span>"timestamp=2011-06-21 17:18:09"<span style="color: #000000;">, </span>"format=json"<span style="color: #000000;">, </span>"uid=67411167"<span style="color: #000000;"> ]</span>
- 將格式化好的參數(shù)鍵值對以字典序升序排列,得到如下參數(shù)集:
<span style="color: #000000;"> [ </span>"format=json"<span style="color: #000000;">, </span>"session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A="<span style="color: #000000;">, </span>"timestamp=2011-06-21 17:18:09"<span style="color: #000000;">, </span>"uid=67411167"<span style="color: #000000;"> ]</span>
- 將前面排序好的參數(shù)集拼接在一起,得到如下字符串:
format<span style="color: #339933;">=</span>jsonsession_key<span style="color: #339933;">=</span>9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A<span style="color: #339933;">=</span>timestamp<span style="color: #339933;">=</span><span style="color: #cc66cc;">2011</span><span style="color: #339933;">-</span><span style="color: #208080;">06</span><span style="color: #339933;">-</span><span style="color: #cc66cc;">21</span> <span style="color: #cc66cc;">17</span><span style="color: #339933;">:</span><span style="color: #cc66cc;">18</span><span style="color: #339933;">:</span>09uid<span style="color: #339933;">=</span><span style="color: #cc66cc;">67411167</span>
- 在拼接好的字符串末尾追加上應用通過OAuth2.0協(xié)議獲取Access Token時所獲取到的session_secret參數(shù)值,得到如下字符串:
format<span style="color: #339933;">=</span>jsonsession_key<span style="color: #339933;">=</span>9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A<span style="color: #339933;">=</span>timestamp<span style="color: #339933;">=</span><span style="color: #cc66cc;">2011</span><span style="color: #339933;">-</span><span style="color: #208080;">06</span><span style="color: #339933;">-</span><span style="color: #cc66cc;">21</span> <span style="color: #cc66cc;">17</span><span style="color: #339933;">:</span><span style="color: #cc66cc;">18</span><span style="color: #339933;">:</span>09uid<span style="color: #339933;">=</span>6741116727e1be4fdcaa83d7f61c489994ff6ed6
- 對前面得到的字符串求MD5簽名,得到的d24dd357a95a2579c410b3a92495f009就是調(diào)用API時所需要的sign參數(shù)值。
接下來便可以通過HTTP POST方法或HTTP GET方法請求Open API的REST服務器,進行接口調(diào)用了,如:
GET /rest/2.0/passport/users/getInfo?session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A%3D×tamp=2011-06-21+17%3A18%3A09&format=json&uid=67411167&sign=d24dd357a95a2579c410b3a92495f009 HTTP/1.1<span style="color: #000000;">Host: openapi.baidu.comUser</span>-<span style="color: #000000;">Agent: Client of Baidu Open PlatformAccept: </span>*<span style="color: #008000;">/*</span><span style="color: #008000;">Accept-Encoding: gzip,deflateAccept-Charset: utf-8Connection: close或POST /rest/2.0/passport/users/getInfo HTTP/1.1Host: openapi.baidu.comUser-Agent: Client of Baidu Open PlatformAccept: </span><span style="color: #008000;">*/</span>*<span style="color: #000000;">Accept</span>-<span style="color: #000000;">Encoding: gzip,deflateAccept</span>-Charset: utf-8<span style="color: #000000;">Content</span>-Length: 179<span style="color: #000000;">Connection: close session_key</span>=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A%3D×tamp=2011-06-21+17%3A18%3A09&format=json&uid=67411167&sign=d24dd357a95a2579c410b3a92495f009
簽名算法實現(xiàn)代碼
PHP代碼實現(xiàn)
獲取簽名的PHP代碼實現(xiàn)方式如下所示:
<span style="color: #008000;">/*</span><span style="color: #008000;">* * 簽名生成算法 * @param array $params API調(diào)用的請求參數(shù)集合的關(guān)聯(lián)數(shù)組,不包含sign參數(shù) * @param string $secret 簽名的密鑰即獲取access token時返回的session secret * @return string 返回參數(shù)簽名值 </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">function</span> getSignature(<span style="color: #800080;">$params</span>, <span style="color: #800080;">$secret</span><span style="color: #000000;">) { </span><span style="color: #800080;">$str</span> = ''; <span style="color: #008000;">//</span><span style="color: #008000;">待簽名字符串 //先將參數(shù)以其參數(shù)名的字典序升序進行排序</span> <span style="color: #008080;">ksort</span>(<span style="color: #800080;">$params</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">遍歷排序后的參數(shù)數(shù)組中的每一個key/value對</span> <span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$params</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$k</span> => <span style="color: #800080;">$v</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">為key/value對生成一個key=value格式的字符串,并拼接到待簽名字符串后面</span> <span style="color: #800080;">$str</span> .= "<span style="color: #800080;">$k</span>=<span style="color: #800080;">$v</span>"<span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;">將簽名密鑰拼接到簽名字符串最后面</span> <span style="color: #800080;">$str</span> .= <span style="color: #800080;">$secret</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">通過md5算法為簽名字符串生成一個md5簽名,該簽名就是我們要追加的sign參數(shù)值</span> <span style="color: #0000ff;">return</span> <span style="color: #008080;">md5</span>(<span style="color: #800080;">$str</span><span style="color: #000000;">); }</span>
調(diào)用示例:
<span style="color: #800080;">$uid</span> = 67411167<span style="color: #000000;">;</span><span style="color: #800080;">$params</span> = <span style="color: #0000ff;">array</span><span style="color: #000000;">( </span>"session_key" => "9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A=", "timestamp" => "2011-06-21 17:18:09", "format" => "json", "uid" => <span style="color: #800080;">$uid</span>,<span style="color: #000000;">);</span><span style="color: #800080;">$sign</span> = getSignature(<span style="color: #800080;">$params</span>, "27e1be4fdcaa83d7f61c489994ff6ed6");
Java代碼實現(xiàn)
獲取簽名的java代碼實現(xiàn)方式如下所示:
<span style="color: #008000;">/**</span><span style="color: #008000;"> * 簽名生成算法 * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> HashMap<String,String> params 請求參數(shù)集,所有參數(shù)必須已轉(zhuǎn)換為字符串類型 * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> String secret 簽名密鑰 * </span><span style="color: #808080;">@return</span><span style="color: #008000;"> 簽名 * </span><span style="color: #808080;">@throws</span><span style="color: #008000;"> IOException </span><span style="color: #008000;">*/</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> String getSignature(HashMap<String,String> params, String secret) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException{ </span><span style="color: #008000;">//</span><span style="color: #008000;"> 先將參數(shù)以其參數(shù)名的字典序升序進行排序</span> Map<String, String> sortedParams = <span style="color: #0000ff;">new</span> TreeMap<String, String><span style="color: #000000;">(params); Set</span><Entry<String, String>> entrys =<span style="color: #000000;"> sortedParams.entrySet(); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 遍歷排序后的字典,將所有參數(shù)按"key=value"格式拼接在一起</span> StringBuilder basestring = <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuilder(); </span><span style="color: #0000ff;">for</span> (Entry<String, String><span style="color: #000000;"> param : entrys) { basestring.append(param.getKey()).append(</span>"="<span style="color: #000000;">).append(param.getValue()); } basestring.append(secret); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 使用MD5對待簽名串求簽</span> <span style="color: #0000ff;">byte</span>[] bytes = <span style="color: #0000ff;">null</span><span style="color: #000000;">; </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> { MessageDigest md5 </span>= MessageDigest.getInstance("MD5"<span style="color: #000000;">); bytes </span>= md5.digest(basestring.toString().getBytes("UTF-8"<span style="color: #000000;">)); } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (GeneralSecurityException ex) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> IOException(ex); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 將MD5輸出的二進制結(jié)果轉(zhuǎn)換為小寫的十六進制</span> StringBuilder sign = <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuilder(); </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < bytes.length; i++<span style="color: #000000;">) { String hex </span>= Integer.toHexString(bytes[i] & 0xFF<span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (hex.length() == 1<span style="color: #000000;">) { sign.append(</span>"0"<span style="color: #000000;">); } sign.append(hex); } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> sign.toString();}</span>
注意:計算簽名時所有參數(shù)的key和value都必須先轉(zhuǎn)換為對應的字符串類型,因為在HTTP請求中傳遞的內(nèi)容都是字符串類型的,很多開發(fā)者都因為沒注意到這點,直接將非字符串類型的參數(shù)的二進制值傳遞了進去,結(jié)果導致簽名與服務端計算的不一致而出錯。
C#代碼實現(xiàn)
獲取簽名的C#代碼實現(xiàn)方式如下所示:
<span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span><span style="color: #808080;">///</span><span style="color: #008000;"> 計算參數(shù)簽名</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span><span style="color: #808080;">///</span> <span style="color: #808080;"><param name="params"></span><span style="color: #008000;">請求參數(shù)集,所有參數(shù)必須已轉(zhuǎn)換為字符串類型</span><span style="color: #808080;"></param></span><span style="color: #808080;">///</span> <span style="color: #808080;"><param name="secret"></span><span style="color: #008000;">簽名密鑰</span><span style="color: #808080;"></param></span><span style="color: #808080;">///</span> <span style="color: #808080;"><returns></span><span style="color: #008000;">簽名</span><span style="color: #808080;"></returns></span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">string</span> getSignature(IDictionary<<span style="color: #0000ff;">string</span>, <span style="color: #0000ff;">string</span>> parameters, <span style="color: #0000ff;">string</span><span style="color: #000000;"> secret){ </span><span style="color: #008000;">//</span><span style="color: #008000;"> 先將參數(shù)以其參數(shù)名的字典序升序進行排序</span> IDictionary<<span style="color: #0000ff;">string</span>, <span style="color: #0000ff;">string</span>> sortedParams = <span style="color: #0000ff;">new</span> SortedDictionary<<span style="color: #0000ff;">string</span>, <span style="color: #0000ff;">string</span>><span style="color: #000000;">(parameters); IEnumerator</span><KeyValuePair<<span style="color: #0000ff;">string</span>, <span style="color: #0000ff;">string</span>>> iterator=<span style="color: #000000;"> sortedParams.GetEnumerator(); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 遍歷排序后的字典,將所有參數(shù)按"key=value"格式拼接在一起</span> StringBuilder basestring= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuilder(); </span><span style="color: #0000ff;">while</span><span style="color: #000000;"> (iterator.MoveNext()) { </span><span style="color: #0000ff;">string</span> key =<span style="color: #000000;"> iterator.Current.Key; </span><span style="color: #0000ff;">string</span> value =<span style="color: #000000;"> iterator.Current.Value; </span><span style="color: #0000ff;">if</span> (!<span style="color: #0000ff;">string</span>.IsNullOrEmpty(key) && !<span style="color: #0000ff;">string</span><span style="color: #000000;">.IsNullOrEmpty(value)){ basestring.Append(key).Append(</span><span style="color: #800000;">"</span><span style="color: #800000;">=</span><span style="color: #800000;">"</span><span style="color: #000000;">).Append(value); } } basestring.Append(secret); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 使用MD5對待簽名串求簽</span> MD5 md5 =<span style="color: #000000;"> MD5.Create(); </span><span style="color: #0000ff;">byte</span>[] bytes =<span style="color: #000000;"> md5.ComputeHash(Encoding.UTF8.GetBytes(basestring.ToString())); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 將MD5輸出的二進制結(jié)果轉(zhuǎn)換為小寫的十六進制</span> StringBuilder result = <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuilder(); </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = <span style="color: #800080;">0</span>; i < bytes.Length; i++<span style="color: #000000;">) { </span><span style="color: #0000ff;">string</span> hex = bytes[i].ToString(<span style="color: #800000;">"</span><span style="color: #800000;">x</span><span style="color: #800000;">"</span><span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (hex.Length == <span style="color: #800080;">1</span><span style="color: #000000;">) { result.Append(</span><span style="color: #800000;">"</span><span style="color: #800000;">0</span><span style="color: #800000;">"</span><span style="color: #000000;">); } result.Append(hex); } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> result.ToString();}</span>
服務器接受請求后,同樣對參數(shù)進行簽名,如果簽名相同則數(shù)據(jù)沒有被修改或者丟失。
注意:計算簽名時所有參數(shù)的key和value都必須先轉(zhuǎn)換為對應的字符串類型,因為在HTTP請求中傳遞的內(nèi)容都是字符串類型的,很多開發(fā)者都因為沒注意到這點,直接將非字符串類型的參數(shù)的二進制值傳遞了進去,結(jié)果導致簽名與服務端計算的不一致而出錯。

熱AI工具

Undress AI Tool
免費脫衣圖片

Undresser.AI Undress
人工智慧驅(qū)動的應用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6
視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

SQL中的Identity是什麼,需要具體程式碼範例在SQL中,Identity是一種用於產(chǎn)生自增數(shù)字的特殊資料類型,它常用於唯一識別表中的每一行資料。 Identity欄位通常與主鍵列搭配使用,可確保每筆記錄都有獨一無二的識別碼。本文將詳細介紹Identity的使用方式以及一些實際的程式碼範例。 Identity的基本使用方式在建立表格時,可以使用Identit

使用Java的String.valueOf()函數(shù)將基本資料型別轉(zhuǎn)換為字串在Java開發(fā)中,當我們需要將基本資料型別轉(zhuǎn)換為字串時,常見的方法是使用String類別的valueOf()函數(shù)。這個函數(shù)可以接受基本資料類型的參數(shù),並傳回對應的字串表示。在本文中,我們將探討如何使用String.valueOf()函數(shù)進行基本資料型別轉(zhuǎn)換,並提供一些程式碼範例來

session失效通常是由於 session 的生存時間過期或伺服器關(guān)閉導致的。其解決方法:1、延長session的生存時間;2、使用持久化儲存;3、使用cookie;4、非同步更新session;5、使用會話管理中介軟體。

char陣列轉(zhuǎn)string的方法:可以透過賦值來實現(xiàn),使用{char a[]=" abc d\0efg ";string s=a;}語法,讓char陣列對string直接賦值,執(zhí)行程式碼即可完成轉(zhuǎn)換。

PHPSession跨域問題的解決方法在前後端分離的開發(fā)中,跨域請求已成為常態(tài)。在處理跨域問題時,我們通常會涉及session的使用和管理。然而,由於瀏覽器的同源策略限制,跨域情況下預設無法共享session。為了解決這個問題,我們需要採用一些技巧和方法來實現(xiàn)session的跨域共享。一、使用cookie跨域共享session最常

1.基於session實作簡訊登入1.1簡訊登入流程圖1.2實作發(fā)送簡訊驗證碼前端請求說明:說明請求方式POST請求路徑/user/code請求參數(shù)phone(電話號碼)回傳值無後端介面實作:@Slf4j@ ServicepublicclassUserServiceImplextendsServiceImplimplementsIUserService{@OverridepublicResultsendCode(Stringphone,HttpSessionsession){//1.校驗手機號碼if

使用Java的String.replace()函數(shù)替換字串中的字元(字串)在Java中,字串是不可變的對象,這意味著一旦創(chuàng)建了一個字串對象,就無法修改它的值。但是,你可能會遇到需要替換字串中的某些字元或字串的情況。這時候,我們可以使用Java的String類別中的replace()方法來實作字串的替換。 String類別的replace()方法有兩種重

無法修補的 Yubico 二因素驗證金鑰漏洞已經(jīng)破壞了大多數(shù) Yubikey 5、安全金鑰和 YubiHSM 2FA 設備的安全性。 Feitian A22 JavaCard和其他使用Infineon SLB96xx系列TPM的裝置也容易受到攻擊。
