接口加密《一》:移动应用中,通过在客户端对访问的url进行加密处理来保护服务器上的数据

我认为,保护服务器端的数据,有这么几个关键点:

  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();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值