


PHP、Java、C#實現(xiàn)URI參數(shù)簽名算法,保準(zhǔn)應(yīng)用與REST服務(wù)器之間的安全通信,防止Secret Key盜用、數(shù)據(jù)篡改等惡意攻擊行為
Jun 13, 2016 pm 12:10 PM
PHP、Java、C#實現(xiàn)URI參數(shù)簽名算法,確保應(yīng)用與REST服務(wù)器之間的安全通信,防止Secret Key盜用、數(shù)據(jù)篡改等惡意攻擊行為
簡介
應(yīng)用基于HTTP POST或HTTP GET請求發(fā)送Open API調(diào)用請求時,為了確保應(yīng)用與REST服務(wù)器之間的安全通信,防止Secret Key盜用、數(shù)據(jù)篡改等惡意攻擊行為,REST服務(wù)器使用了參數(shù)簽名機(jī)制。應(yīng)用在調(diào)用Open API之前,需要為其所有請求參數(shù)計算一個MD5簽名,并追加到請求參數(shù)中,參數(shù)名為“sign”。REST服務(wù)器在接收到請求時會重新計算簽名,并判斷其值是否與應(yīng)用傳遞過來的sign參數(shù)值一致,以此判定當(dāng)前Open API調(diào)用請求是否是被第三者偽造或篡改。
應(yīng)用在調(diào)用Open API之前需要通過?OAuth2.0服務(wù)獲得用戶或平臺的授權(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個:
- 如果是通過應(yīng)用服務(wù)端調(diào)用Open API,則注冊應(yīng)用時所拿到的應(yīng)用密鑰(即API Key)就是參數(shù)簽名密鑰;
- 如果是通過JavaScript、ActionScript等客戶端語言調(diào)用Open API,則應(yīng)用獲取到用戶授權(quán)后所拿到的Session Secret就是參數(shù)簽名密鑰。當(dāng)然,通過服務(wù)端調(diào)用Open API時也可以用Session Secret作為簽名密鑰。
簽名算法
假設(shè)參與參數(shù)簽名計算的請求參數(shù)分別是“k1”、“k2”、“k3”,它們的值分別是“v1”、“v2”、“v3”,則參數(shù)簽名計算方法如下:
- 將請求參數(shù)格式化為“key=value”格式,即“k1=v1”、“k2=v2”、“k3=v3”;
- 將格式化好的參數(shù)鍵值對以字典序升序排列后,拼接在一起,即“k1=v1k2=v2k3=v3”;
- 在拼接好的字符串末尾追加上應(yīng)用通過OAuth2.0協(xié)議獲取Access Token時所獲取到的session_secret參數(shù)值;
- 上述字符串的MD5值即為簽名的值。
注意:計算簽名時的請求參數(shù)中不要包含sign(簽名)參數(shù),因為sign參數(shù)的值此時還不知道,有待計算。
另外,計算簽名的時候不需要對參數(shù)進(jìn)行urlencode處理(“application/x-www-form-urlencoded”編碼),但是發(fā)送請求的時候需要進(jìn)行urlencode處理,這是很多開發(fā)者最容易犯錯的地方。
簽名過程示例
假設(shè)某個應(yīng)用需要獲取某個uid為67411167的用戶的基本資料,應(yīng)用在之前的通過OAuth2.0服務(wù)獲取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')來獲取當(dāng)前系統(tǒng)時間)為"2011-06-21 17:18:09",希望REST服務(wù)器以JSON格式返回調(diào)用結(jié)果,即相當(dāng)于參與參數(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>
- 在拼接好的字符串末尾追加上應(yīng)用通過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服務(wù)器,進(jìn)行接口調(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ù)名的字典序升序進(jìn)行排序</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ù)名的字典序升序進(jìn)行排序</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輸出的二進(jìn)制結(jié)果轉(zhuǎn)換為小寫的十六進(jì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)換為對應(yīng)的字符串類型,因為在HTTP請求中傳遞的內(nèi)容都是字符串類型的,很多開發(fā)者都因為沒注意到這點,直接將非字符串類型的參數(shù)的二進(jìn)制值傳遞了進(jìn)去,結(jié)果導(dǎo)致簽名與服務(wù)端計算的不一致而出錯。
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ù)名的字典序升序進(jìn)行排序</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輸出的二進(jìn)制結(jié)果轉(zhuǎn)換為小寫的十六進(jì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>
服務(wù)器接受請求后,同樣對參數(shù)進(jìn)行簽名,如果簽名相同則數(shù)據(jù)沒有被修改或者丟失。
注意:計算簽名時所有參數(shù)的key和value都必須先轉(zhuǎn)換為對應(yīng)的字符串類型,因為在HTTP請求中傳遞的內(nèi)容都是字符串類型的,很多開發(fā)者都因為沒注意到這點,直接將非字符串類型的參數(shù)的二進(jìn)制值傳遞了進(jìn)去,結(jié)果導(dǎo)致簽名與服務(wù)端計算的不一致而出錯。

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

What is Identity in SQL? Specific code examples are needed. In SQL, Identity is a special data type used to generate auto-incrementing numbers. It is often used to uniquely identify each row of data in a table. The Identity column is often used in conjunction with the primary key column to ensure that each record has a unique identifier. This article will detail how to use Identity and some practical code examples. The basic way to use Identity is to use Identit when creating a table.

Convert basic data types to strings using Java's String.valueOf() function In Java development, when we need to convert basic data types to strings, a common method is to use the valueOf() function of the String class. This function can accept parameters of basic data types and return the corresponding string representation. In this article, we will explore how to use the String.valueOf() function for basic data type conversions and provide some code examples to

Session failure is usually caused by the session lifetime expiration or server shutdown. The solutions: 1. Extend the lifetime of the session; 2. Use persistent storage; 3. Use cookies; 4. Update the session asynchronously; 5. Use session management middleware.

Method of converting char array to string: It can be achieved by assignment. Use {char a[]=" abc d\0efg ";string s=a;} syntax to let the char array directly assign a value to string, and execute the code to complete the conversion.

Solution to the cross-domain problem of PHPSession In the development of front-end and back-end separation, cross-domain requests have become the norm. When dealing with cross-domain issues, we usually involve the use and management of sessions. However, due to browser origin policy restrictions, sessions cannot be shared by default across domains. In order to solve this problem, we need to use some techniques and methods to achieve cross-domain sharing of sessions. 1. The most common use of cookies to share sessions across domains

1. Implementing SMS login based on session 1.1 SMS login flow chart 1.2 Implementing sending SMS verification code Front-end request description: Description of request method POST request path /user/code request parameter phone (phone number) return value No back-end interface implementation: @Slf4j@ ServicepublicclassUserServiceImplextendsServiceImplimplementsIUserService{@OverridepublicResultsendCode(Stringphone,HttpSessionsession){//1. Verify mobile phone number if

Replace characters (strings) in a string using Java's String.replace() function In Java, strings are immutable objects, which means that once a string object is created, its value cannot be modified. However, you may encounter situations where you need to replace certain characters or strings in a string. At this time, we can use the replace() method in Java's String class to implement string replacement. The replace() method of String class has two types:

An unpatchable Yubico two-factor authentication key vulnerability has broken the security of most Yubikey 5, Security Key, and YubiHSM 2FA devices. The Feitian A22 JavaCard and other devices using Infineon SLB96xx series TPMs are also vulnerable.All
