PHP实现“挑战-应答”方式的登陆模型

PHP实现“挑战-应答”方式的登陆模型

 

内容提要

1.       什么是“挑战-应答”系统

2.       关于HMAC

3.       登陆模型的流程与相关代码

 

“挑战-应答”

定义  假设用户U想向系统S认证自己。设US有一个协商好的秘密函数f。挑战-应答认证系统就是这样的一个系统:S发送一个随机消息m(挑战)给U,用户U回应m的变形r=fm(应答)。S通过独立计算r来验证r

根据上面的定义,在一个典型的C/S系统中,认证过程为:

1) 客户向认证服务器发出请求,要求进行身份认证;

2) 认证服务器从用户数据库中查询用户是否是合法的用户,若不是,则不做进一步处理;

3) 认证服务器内部产生一个随机数,作为“提问”,发送给客户;

4) 客户将用户名字和随机数合并,使用单向Hash函数(例如MD5算法)生成一个字节串作为应答;

5) 认证服务器将应答串与自己的计算结果比较,若二者相同,则通过一次认证;否则,认证失败;

6) 认证服务器通知客户认证成功或失败。

 

在一个C/S项目中,1~3步其实就是客户端请求登陆页面的一个过程。到了第四步,客户将用户名字和随机数合并,生成应答,这个过程使用服务器计算的方式还是在客户端就计算好?使用的函数fm又是怎样选择呢?在这里我的办法是,引入HMAC

HMAC

定义  计算HMAC需要一个散列函数hash(可以是md5或者sha-1)和一个密钥key。用L表示hash函数输出字符串长(md516),用B表示数据块的长度(md5sha-1的分割数据块长都是64)。密钥key的长度可以小于等于数据块长B,如果大于数据块长度,可以使用hash函数对key进行转换,结果就是一个L长的key

然后创建两个B长的不同字符串:

  innerpad = 长度为B 0×36

  outterpad = 长度为B 0×5C

计算输入字符串strHMAC

  hash(key ^ outterpad, hash(key ^ innerpad, str))

HMAC主要应用在身份验证中,它的使用方法是这样的:

  1. 客户端发出登录请求(假设是浏览器的GET请求)

  2. 服务器返回一个随机值,并在会话中记录这个随机值

  3. 客户端将该随机值作为密钥,用户密码进行HMAC运算,然后提交给服务器

  4. 服务器读取用户数据库中的用户密码和步骤2中发送的随机值做与客户端一样的HMAC运算,然后与用户发送的结果比较,如果结果一致则验证用户合法。

在这个过程中,可能遭到安全攻击的是服务器发送的随机值和用户发送的HMAC结果,而对于截获了这两个值的黑客而言这两个值是没有意义的,绝无获取用户密码的可能性,随机值的引入使HMAC只在当前会话中有效,大大增强了安全性和实用性。大多数的语言都实现了HMAC算法,比如phpmhashpythonhmac.pyjavaMessageDigest类,在web验证中使用HMAC也是可行的,用js进行md5运算的速度也是比较快的。事实上,客户端中使用js来计算HMAC,返回结果,在服务器中调用PHPmhash计算后与客户端返回的结果进行比较。这样做的好处是,服务器避免了重复计算,而且由于客户端在返回结果的过程中,所提交的数据由于经过了HAMC的加密,安全性得到了进一步的提高。

根据上面的分析,整个登陆的模型的设计已经十分明了。由于项目采用的是PHP做为开发语言,同时采用Codeigniter作为框架。相关代码如下:

1.       客户向认证服务器发出请求,要求进行身份认证。这个过程由Login Controller控制,返回用户登陆的页面。其中客户端用来计算HMACjs可以在这里下载:

http://pajhome.org.uk/crypt/md5/md4.js

LoginView.php

  1. <?php $attributes = array('onSubmit'=>'javascript:return hex_hmac_md5(key.value, hex_md5(password.value))',
  2. 'name' => 'loginForm''id' => 'loginForm'); ?>
  3. <?=form_open('login/check_user'$attributes);?>
  4. //HTML代码,略
  5. <tr>
  6. <td width="30%" align="right" height="25">用户名:</td>
  7. <td align="left"><input name="username" value="" type="text" style="width:130px;" /></td></tr>
  8. <tr><td align="right" height="25">密 码:<input type="hidden" name="key" value=<?=$key; ?> /></td>
  9. <td align="left"><input name="password" type="password" value="" style="width:130px;" /></td></tr>

      实现的效果如下:

<script src="http://pajhome.org.uk/crypt/md5/md4.js" type=text/javascript></script>

User name
Password

2.       服务器端校验同样由Login Controller控制,调用Model,查询数据库,同时进行校验,并将结果返回Login Controller

LoginModel.php

  1. <?php
  2. class user_model extends Model {
  3.     
  4.     function __construct() {
  5.         parent::Model();
  6.         //$this->load->library('Hex_hmac');
  7.     }
  8.     
  9.     function authenticate($username,$password,$key){
  10.         $query = $this->db->get_where('user',array('username'=>$username));
  11.         $admin = $query->row();
  12.         if($admin){
  13.             $data=$admin->password;
  14.             $result=bin2hex (mhash(MHASH_MD5, $data, $key));
  15.             // 若没有启用php_mhash.dll(libmhash) 扩展则使用下面的方式
  16.             //$result=$this->hex_hmac->hmac($key,$data);            
  17.             if($result == $password){
  18.                 return TRUE;
  19.             }else{
  20.                 return FALSE;
  21.             }
  22.         }else{
  23.             return FALSE;
  24.         }
  25.     }
  26.     
  27.     // 一般的登陆模型:
  28.     function validate_user($username,$password){
  29.         // 用select 1 比较好
  30.         $sql="SELECT 1 FROM user WHERE username = ? AND password = md5(?)";
  31.         $query=$this->db->query($sql,array('username'=>$username,'password'=>$password));
  32.         if ($query->num_rows() > 0) {
  33.             return TRUE;            
  34.         } else {
  35.             return FALSE;
  36.         }
  37.     }
  38. }
  39. ?>

生成SALT的函数如下:

  1. function createKey($length=6) {
  2.     $hash = '';
  3.     $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
  4.     $max = strlen ( $chars ) - 1;
  5.     mt_srand ( ( double ) microtime () * 1000000 );
  6.     for($i = 0; $i < $length$i ++) {
  7.         $hash .= $chars [mt_rand ( 0, $max )];
  8.     }
  9.     return $hash;
  10. }

问题与分析

1.       JavaScript被禁用的情形。很明显,这种情况发生的几率很大——无论是用户的浏览器不支持还是浏览器的设置。在这种情况下,解决的方法只有两个:一是屏蔽这类用户的登陆;二是采用没加密的方式登陆。很明显,采用后者是一个明智的做法,因为这只需对现有的模型稍作修改即可。例如,在用户输入的表单中,增加一个“Hidden field”,在服务器端校验用户返回的结果前,先检查这个“Hidden field”是否经过Hash计算,如否则采用服务器进行两次计算的方式进行校验的过程。

2.       空值问题。这几乎不算一个问题,如果在一般的登陆模型中的话,因为无论是采用客户端js校验或者服务器端校验都能很好解决这个问题。而在这个登陆模型中,事情有一点不一样:经过HMAC计算,即使是空值,也能返回结果!当然,在只需在客户端中增加一段校验的js即可。

3.       SALT的产生。SALT由于在客户端和服务器端中要保持同步,产生SALT的函数在登录的过程中务必确保只调用了一次。这里的实现方法是,在登陆中增加一个SALT的“Hidden field”,提交结果的同时,也提交SALT供服务器计算。

4.       Password字段的加密。很明显,采用了“挑战-应答”的登陆方式,由于在返回数据之前就已经进行加密,安全性得到很大的提高。而在一般的网站系统中都采用了md5作为加密的方式。但是目前一些网站提供了关于MD5解密后密码和明密的数据库记录,有些网站的记录有上亿条,可以很快查出一次加密后的明码。针对一些弱口令,据说已经达到70%左右。因此采用了md5重复加密的方式,但是由于md5采用单向加密,相同的明文,密码一定是一样的。为什么不能采用与登陆时采用的HMAC?显然,由于HMAC需要随机的SALT,这增加了解密的复杂度。

3.       “挑战”即SALT的生成,这里采用的是用PHP生成随机字符串并传到客户端的办法。HMAC与一般的加密重要的区别在于它具有“瞬时”性,即认证只在当时有效。因此用户每次登陆,都将产生新的SALT,故这里并没用加密SALT

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值