http接口加密《一》:移動應用中,通過在客戶端對訪問的url進行加密處理來保護服務器上的數據


來源:http://meiyitianabc.blog.163.com/blog/static/10502212720131056273619/

我認為,保護服務器端的數據,有這么幾個關鍵點:

  1. 不能對使用體驗產生影響,這就排除掉了諸如每次接口調用都要求用戶輸入驗證碼這樣的做法
  2. 接口調用的網絡交互需要無規律可循,比如article/1 –> article/1000 這樣的接口就太容易被其他人爬走了
  3. 要嚴格意義上阻擊爬蟲,需要每一次網絡請求都是不可重放的,這樣才能避免其他人通過監聽網絡交互並重放來爬取數據
  4. 對服務器端編碼不產生太大影響,如果要對服務器端傷筋動骨的大改,肯定是要不得的

通常,我們會采用一種簡單有效的方法:對服務器返回的數據加密來解決,但是,這種做法並沒有解決上面所提到的第二點,接口調用的時候url的規律性太強,網絡監聽一下數據,就很容易找到url地址的規律了,加密的破解也很簡單,反編譯直接定位到解密函數,拿到密鑰。當然,在強大的反編譯工程面前,一切努力都是徒勞的,不管你用何種方法,都是可以把中間的邏輯找到並模擬成一個客戶端來爬數據的。

我下面就提出一個破解更加復雜一些的方法,在客戶端產生請求時,對接口url進行RSA加密處理。

假設我們本來需要訪問 http://api.example.com/articles 這樣的一個接口,接口返回json數據。在客戶端訪問之前,我們先對這個url進行這樣的處理:

  1. 加客戶端時間戳:http://api.example.com/1322470148/articles
  2. 對url的path段進行rsa加密,然后base64:http://api.example.com/TBhIskCgCN+WMK3PftbYzPQFAKvx9sE9OMOxvL00kCBlNiKw2C1Mb7oGcfUepTxauG06NLBNhr5BFtjt7Xu7uwdpUYyVcFRdI37SVyGRCOzaxACOGXGpX5dHZqQJia0icxwWJ+D1RiJqxFWQ++3/IgUOgDzgvQnPIl420bpztB8=

我們真實訪問的地址就變成了這樣一個長長的 url 結構,我們通過rsa算法的padding參數和時間戳,就可以讓這個后面長長的bas64串在每次訪問的時候都發生變化,同時,我們可以在服務器端把一個小時之內的請求過的串都記下來,並不讓再次訪問,這樣就防止了爬蟲的重放請求嘗試。

在服務器端,我們就需要在做響應之前,把url還原回來。在服務器端,現在都是框架的天下,一般都有唯一的入口,如果使用的是php語言,主要在入口的index.php加上一些代碼就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
if ( $_SERVER [ 'HTTP_HOST' ] == "api.example.com" ){ // 只針對api這個域名做處理
     include_once dirname( __FILE__ ). '/protected/components/EncryptUtil.php' ; // 加解密庫,你需要實現你自己的加解密類
     $request_uri = $_SERVER [ 'REQUEST_URI' ];
     if (isset( $_SERVER [ 'HTTP_HOST' ])){
         if ( strpos ( $request_uri , $_SERVER [ 'HTTP_HOST' ])!==false){
             // 把 REQUEST_URI 中可能包含的host信息去除掉
             $request_uri =preg_replace( '/^\w+:\/\/[^\/]+/' , '' , $request_uri );
         }
     }
     $encoded = base64_decode ( substr ( $request_uri , 1));
     if ( $encoded && strlen ( $encoded ) % 128 ===0){
         $real_uri = EncryptUtil::private_decrypt( $encoded );         // 解密url路徑
         if (! $real_uri ){ echo ":)" ; return ; }                        // 解密失敗
         if (preg_match( "/([0-9]+)\\/(.+)/" , $real_uri , $matches )){   // 提取出時間戳和真實的url請求地址
             $timestamp = $matches [1];                               // 客戶端請求的時間戳
             $real_uri = $matches [2];                                // 客戶端請求的真實地址
             $_SERVER [ 'REQUEST_URI' ] = $real_uri ;                    // 置上本來應該有的全局$_SERVER['REQUEST_URI']
             if (preg_match( "/^[^?]+\\?(.+)/" , $real_uri , $matches )){
                 $_SERVER [ 'QUERY_STRING' ] = $matches [1];             // 置上本來應該有的全局$_SERVER['QUERY_STRING']
                 parse_str ( $_SERVER [ 'QUERY_STRING' ], $array );
                 $_REQUEST = array_merge ( $_REQUEST , $array );         // 置上本來應該被設置的全局$_REQUEST
                 $_GET = array_merge ( $_GET , $array );                 // 置上本來應該被設置的全局$_GET
             }
         } else { // url的格式不符合,沒有包含時間戳
             echo ":)" ; return ;
         }
     } else { // url的長度不符合規則
         echo ":)" ; return ;
     }
}

在經過這樣一段代碼處理之后,框架就一切正常,其他代碼都不需要做變更,就有了rsa加密的url支持,當然,這幾行代碼還是不能阻止重放攻擊的,里面並沒有對請求過的url進行記錄處理,要實現url訪問的唯一性,還需要額外的更多代碼。

服務器端完成了,那客戶端也同樣需要做相應操作,我這里就不詳細講解了,貼上一段修改過的實際運行的代碼,IOS,應用了 three20庫,並兼容TTURLRequest緩存機制。

 

Android的Java版本我就把實際運行中的代碼的http部分抽離出來,因為牽涉到一些相關配置,代碼不能正常編譯,不過也放在這里,以供參考。

android-rsa-http.zip下載地址

用法示例:

1
2
3
BaiyiApiRequest request = new BaiyiApiRequest( "articles/1" );
request.setListener( this );
request.start();

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com