API接口签名验证

    本文地址:http://www.tongxinmao.com/Article/Detail/id/111

    就安全来说,所有客户端和服务器端的通信内容应该都要通过加密通道(HTTPS)传输,明文的HTTP通道将会是man-in-the- middle及其各种变种攻击的温床。所谓man-in-the-middle攻击简单讲就是指恶意的黑客可以在客户端和服务器端的明文通信通道上做手 脚,黑客可以监听通信内容,偷取机密信息,甚至可以篡改通信内容,而通过加密后的通信内容理论上是无法被破译的。

    URL签名生成规则

    API的有效访问URL包括以下三个部分: 
    1. 资源访问路径,如/v1/deal/find_deals; 
    2. 请求参数:即API对应所需的参数名和参数值param=value,多个请求参数间用&连接如deal_id=1-85462&appid=00000; 
    3. 签名串,由签名算法生成

    签名算法如下:

    1. 对所有请求参数进行字典升序排列; 
    2. 将以上排序后的参数表进行字符串连接,如key1value1key2value2key3value3...keyNvalueN; 
    3. app secret作为后缀,对该字符串进行SHA-1计算,并转换成16进制编码; 
    4. 转换为全大写形式后即获得签名串

     

    注意:请保证HTTP请求数据编码务必为UTF-8格式,URL也务必为UTF-8编码格式。

     

    举个实例:

    PHP服务端先要给开发者(APP)分配一个appid与appsecret (正常情况下,开发者要到服务提供商的官网申请),作为客户端,需要保留好官方颁发的appid & appsecret 

    appid会在请求中作为一个应用标识参与接口请求的参数传递,appsecret 将作为唯一不需要参数传递,但是它将作为验证当前请求的关键参数,只有应用开发者和颁发的服务端才知道。由于签名是依靠同样的算法加密实现,因此,应用端和服务端可以计算出相同的签名值,签名实际意义在于服务端对客户端的访问身份认证。在某种意义上签名机制有点类似用公钥方法签名,用每个应用对应的私钥值来解密,只是这种解密过程实质就是核对签名参数值的过程。
    假设分配:

    $appid=5288971;$appsecret= 'r5e2t85tyu142u665698fzu';

    移动客户端,需要请求服务列表(以下代码可以为java或sf等移动端编写)
    请求地址: http://web.com/server/list

    参数:

    $array=['appid'=>5288971,
    'menu'=>'客户服务列表',
    'lat'=>21.223,
    'lng'=>131.334
        ];

     

    对应签名算法

    复制代码

    // 1. 对加密数组进行字典排序foreach ($array as $key=>$value){ $arr[$key] = $key; 
     } sort($arr); //字典排序的作用就是防止因为参数顺序不一致而导致下面拼接加密不同
     
     // 2. 将Key和Value拼接
     
     $str = "";foreach ($arr as $k => $v) { $str = $str.$arr[$k].$array[$v];
    }//3. 通过sha1加密并转化为大写
    //4. 大写获得签名$restr=$str.$appsecret;$sign = strtoupper(sha1($restr));

    复制代码

     

    public static function sign($appSecret, $params) {
        if (!is_array($params))
            $params = array();
     
        ksort($params);
        $text = '';
        foreach ($params as $k => $v) {
            $text .= $k . $v;
        }
     
        return md5($appSecret . $text . $appSecret);

    }


    //http://mp.weixin.qq.com/wiki/8/f9a0b8382e0b77d87b3bcc1ce6fbc104.html

    private function checkSignature()
    {
            $signature = $_GET["signature"];
            $timestamp = $_GET["timestamp"];
            $nonce = $_GET["nonce"];	
            		
    	$token = TOKEN;
    	$tmpArr = array($token, $timestamp, $nonce);
    	sort($tmpArr, SORT_STRING);
    	$tmpStr = implode( $tmpArr );
    	$tmpStr = sha1( $tmpStr );
    	
    	if( $tmpStr == $signature ){
    		return true;
    	}else{
    		return false;
    	}
    }


    将生产的sign签名一起写入array中,通过约定好的method方式发送参数到请求接口

    $array['sign']=$sign;

     

    打印$array

    复制代码

    Array(
        [appid] => 5288971
        [menu] => 客户服务列表
        [lat] => 21.223
        [lng] => 131.334
        [sign] => C096D7811E944386CE880597BA334A5AB640B088
    )

    复制代码

    客户端将数据封装xml或Json发送到服务端,服务端先解析

    {"appid":5288971,"menu":"\u5ba2\u6237\u670d\u52a1\u5217\u8868","lat":21.223,"lng":131.334,"sign":"C096D7811E944386CE880597BA334A5AB640B088"}
    $serverArray= json_decode($json,TRUE);

    服务端查询appid对应的密钥

    $model=Model::find()->where("appid=:appid")->params([":appid"=>$serverArray['appid']])->one(); if($model){   $serverSecret=$model->appsecret;
    }

     按照相同的字典排序与算法生成服务端的$sign ,判断$sign 是否相同。

    复制代码

    $clientSign=$array['sign'];unset($serverArray['sign']);#生成服务端str$serverstr = "";foreach ($serverArray as $k => $v) { $serverstr = $str.$k.$v;
    }$reserverstr=$str.$serverSecret;$reserverSign = strtoupper(sha1($reserverstr));if($clientSign!=$reserverSign){    die('非法请求');
    }else{//    your code
        continue;
    }

    复制代码

     

    在仅适用短信登录做手机端app时,可以设置secret的过期时间,短信登录后,保存appid(userid)与密钥secret,每当用户打开APP时,先联网请求登录是否过期,过期重新短信登录获取新的secret。

     

    附加:

    有时,我们使用hash_hmac进行加密(我们项目中使用……)

    复制代码

    /*
     * 生成签名,$args为请求参数,$key为私钥 */function makeSignature($args, $key)
    {    if(isset($args['sign'])) {        $oldSign = $args['sign'];        unset($args['sign']);
        } else {        $oldSign = '';
        }    ksort($args);    $requestString = '';    foreach($args as $k => $v) {        $requestString .= $k . '=' . urlencode($v);
        }    $newSign = hash_hmac("md5",strtolower($requestString) , $key);    return $newSign;
    }

    复制代码

     

    在签名的过程中,起到决定性作用之一的是appsecret,因此如何保存成为关键。我们分类讨论。

    接口调用方的代码跑在服务器的情况比较好办,除非服务器被攻陷,否则外接无法知道appsecret,当然,要注意不能往日志里写入appsecret的值,其他敏感值也禁止写入日志,如账号密码等信息。

    假如是客户端请求接口,就需要多想一步了。假如把appsecret硬编码到客户端,会有反编译的风险,特别是android。可以在客户端登陆验证成功后,返回给客户端的信息中带上appsecret(当然,返回的数据也可能被拦截,真是防不胜防啊。。。)。特别说明一下,在android开发中,假如硬要把appsecret硬编码,建议把appsecret放到NDK中编译成so文件,app启动后去读取。

    TOTP:Time-based One-time Password Algorithm(基于时间的一次性密码算法)

    在一些小型项目中,可能不需要复杂的签名校验,只需要做调用方的身份验证。TOTP (rfc6238)即可满足。

    TOTP是基于时间的一次性算法,客户端和服务器端约定秘钥,加入时间作为运算因子得到一个6位数字。客户端请求服务端时生成一个6位数字,服务端使用相同算法验证这个6位数字是否合法。

    下面再展开一下讨论,跟本文讨论的主题关系不大。

    TOTP允许客户端和服务器端存在时间误差,如口令在N分钟内有效。给出一下源码供大家参考:

    与TOTP类似的还有HTOP,它是基于次数的验证算法。这里不展开讨论。

    请输入图片描述

    基于TOTP还有很多应用,例如动态的登录口令。用户登录时,除了需要输入设置的密码外,还需要输入动态密码,每个用户的秘钥都不一样,用户的手机端安装一个app即可实现,动态口令每N分钟变化一次。android客户端在各大应用市场搜索 google-authenticator,如百度应用市场,ios客户端在appstore也可搜索得到。下面给出部分下载链接:

    支付宝、QQ令牌、银行客户端等这些手机客户端中也有类似的应用,在验证密码之后会多出一道动态口令的验证,他们使用的方案都类似于google-authenticator。

    大公司的运维人员,甚至是所有员工登录内部OA系统(单点登录),都需要PIN+令牌码的双重验证(PIN是自行设置的固定密码,令牌码则是动态口令码),他们通常使用RSA SecurID双因素动态口令身份认证解决方案。

    请输入图片描述


    上一篇:MPRO编程器USB信息
    下一篇:C语言 FIFO(宏)