Monthly Archive for 八月, 2010

廉价OTP解决方案:yubikey

可到http://yubikey.taobao.com/购买

@ohsc那里了解到yubikey这个东西。作为信息安全的学生,我的第一反应是:两眼发亮。为什么两眼发亮呢?慢慢解释。

首先要从什么是OTP说起。
OTP是One-Time Password的简称,中文对应的准确翻译应该叫“一次一密”。根据香农(这个人…信息论、现代密码学,以及其他乱七八糟东西的发明者,牛逼至极的人物啊!)创立的现代密码学理论,一次一密是不可能被破解的,除此之外所有的其他加密方案都至少能被暴力破解,虽然破解的难度是指数级的。(当然,香农说的一次一密不可破解是有前提条件的,具体参考维基百科上的”一次性密码本“词条)。具体来说,一次一密就是在每次加密会话过程中,对传送的明文使用不同的密钥进行加密,每次使用的密钥是随机、不可预测、不重复使用的,从而保证了加密是绝对不可破解的。
当然,我说的是简单化的一次一密的实现,实际操作过程中,密钥的长度必须大于等于明文长度,而且密钥必须是可验证的(所以也可以看成是可预测的)。真正的一次一密,等于需要一个安全信道来传送不少于明文长度的密钥,几乎是没有意义的。

其实生活中已经有很多一次一密的例子,例如中国银行的E-TOKEN、魔兽世界安全令牌、中国建设银行的动态口令卡(纸制),这些都是一次一密的。

国内的一次一密电子令牌基本上使用的是SecurID,EMC公司下属的RSA公司出品的。这种一次一密解决方案使用一个预先定义的time-based的函数计算密码,称为Time-synchronized one-time password。每个token出厂前与服务器同步时间,在出厂后,token和服务器以相同的时钟计算,每分钟变化一次。

在验证token上读取的数字时,服务端可以设置允许的误差值,例如允许前后5分钟内产生的密钥输入都认为是有效的,这样可以最大限度避免token的时钟漂移。

经仔细查证后,上文描述有误。准确的实现是:RSA的服务端验证时允许前后30秒或前后60秒内的密钥为有效密钥。服务端会保留当前token,前一token,后一token三个token值,如果用户输入与当前token一致则不做变化。如果用户输入的是前一token或后一token,服务器将记录这个SecurID的ID对应的offset,下次验证时考量这个offset。如果用户超过了这三个token的值范围,用户将被提示输入SecurID显示的下一个token,如果与上一个输入的token顺序一致,系统将判断验证通过,并记录这个较大的offset值。

这样的方案安全性很高,很多公司内部的vpn系统、线上服务器登陆系统等等都使用了SecurID进行认证。缺点很明显:价格高,据说每个的成本在¥200(网易出的那个山寨版我就不评论了…售价居然才60左右…只能说不太靠谱了…),而且需要配套RSA的服务端方案;需要电池保证时钟运作,因此存在更换token的情况。

另一种完全不同的一次一密方案是使用数学方法生成密钥序列,称为Mathematical-algorithm-based one-time password。这种算法使用数学函数生成密钥序列,然后依次使用密钥序列,从而不需要考虑时间因素。由于密钥序列生成过程中没有时间因素,实际生产的硬件token可以不包含电池供电,免去服务端与token的对时,成本可以下降,同时也没有电池耗尽更换token的担忧(更换token=安全隐患,因为需要人的参与,人永远是任何安全体系的最大漏洞…)。维基百科上举例了一个使用hash chain方法产生此类OTP的方案:找到一个单向函数f(例如任意一个hash函数),给定一个初始种子s。根据f和s产生序列:f(s), f(f(s)), f(f(f(s)))…。然后将该序列倒置,依次作为密钥使用。第三方如果偶然获取到序列中某个密钥,他就必须要通过f的反函数来计算下一个密钥,而f的单向性保证了这样的计算是不可行的。

yubikey使用的是第二种方案。它是一个廉价的OTP解决方案,10枚yubikey的平均价格是$28,由yubico提供验证服务器并提供API供开发者调用。它不需要电池供电,也没有液晶屏显示密钥。在插入电脑之后,它会被识别为一个USB键盘,在任意输入框聚焦并按一下yubikey上的硬件按钮,yubikey将自动模拟键盘输入一串一次性密钥。由于yubikey在电脑上被识别为USB键盘,它可以做到最好的免驱动支持(现在没有操作系统不支持USB键盘了吧?),可以在Windows/MacOSX/Linux上使用。关于yubikey使用的OTP生成方案,强烈建议阅读一下官方提供的文档来了解:http://yubico.com/files/Security_Evaluation_2009-09-09.pdf

要注意的是,yubikey每次插拔都会在内置的非易失性存储器上的Session Counter增加1,该Session Counter总长度为16bit,在每天插拔20次的频率下,该Counter在9年左右到达满值。这个寿命还是很让人满意的。

yubikey是一个非常开放的硬件设备,它本身的硬件实现原理、服务端架设代码、服务端API代码,都是完全开放的。从而在它之上衍生了很多开源项目与之搭配使用。从yubico的wiki页面可以看到一个简单的列表。稍微列举一下:

  • YubiRADIUS:使用yubikey产生的OTP验证用户的RADIUS服务器。该服务器由yubico提供,任何拥有yubikey的用户都可以在上面开通管理员帐号进行使用。详细使用指南:http://wiki.yubico.com/wiki/index.php/Applications:YubiRADIUS_RADIUS_Service
  • RADIUS on Premise:使用LDAP维护yubikey ID与用户的联系,自行搭建使用yubikey做验证的RADIUS服务器。OTP+VPN+RADIUS,这几乎是最安全的上网方案了。
  • YubiKey WordPress Plugin:wordpress登录过程中可使用yubikey配合原密码进行加强安全的登录
  • LastPass Login:使用yubikey作为lastpass的master password,详见@ohsc的日志。该功能需lastpass的高级会员权限,每月$1。
  • Google Apps:使用yubikey登录Google Apps。要是Gmail也能用yubikey登录,那就nb了…

Twitter2Renren PHP同步脚本(20100828更新)

又更新了一下,使用m.renren.com进行状态发布

脚本名称正式定为twitter2renren.php

使用方法详见这篇日志

代码:

< ?php
 
//
//twitter2renren.php
//By @yegle, yegle.net
//Licensed under CC by-nc-sa
//Version:20100828
//
 
$file = '/home/yegle/.twitter2renren';
$cookie_file = '/home/yegle/.twitter2renren.cookie';
$twitter_username = '';
$twitter_pwd = '';
$hashtag = '';
$renren_username = '';
$renren_password = '';
 
//配置完毕,请停止编辑文件!
if(file_exists($file)){
    $lastid = intval(file_get_contents($file));
}
else{
    touch($file);
    $lastid = 0;
}
 
 
$timeline_url = 'http://'.$twitter_username.':'.$twitter_pwd.'@twitter.com/statuses/user_timeline.rss?count=200';
if($lastid!==0) $timeline_url.='&since_id='.$lastid;
 
 
$timeline = file_get_contents($timeline_url);
 
$obj = simplexml_load_string($timeline);
 
$items = $obj->channel->item;
if(empty($items)){
    echo "no newer tweet\n";
    exit();
}
if(sizeof($items)==1){
    $new_id = preg_replace('/.*\/([0-9]*)$/','$1',strval($items->link));
}
else{
    $new_id = preg_replace('/.*\/([0-9]*)$/','$1',strval($items[0]->link));
}
if($new_id == ''){
    echo "error\n";
    exit();
}
file_put_contents($file,$new_id);
$post_arr = array();
foreach($items as $tweet){
    $text = substr($tweet->title,strlen($twitter_username)+2);
    if(strpos($text,'@')!==0 && ( empty($hashtag) || strpos($text,$hashtag)!==FALSE)){
        $text = str_replace("RT: @","转自",$text);
         $post_arr[] = $text.' [twitter]';
    }
}
 
if(empty($post_arr)){
    echo "no need.\n";
    exit();
}
 
$post_arr = array_reverse($post_arr);
 
foreach($post_arr as $item){
    $post = 'sour=home&status='.urlencode($item).'&update=发布';
 
    $renren_login = "http://3g.renren.com/login.do?fx=0&autoLogin=true";
    $ch = curl_init();
    curl_setopt($ch,CURLOPT_COOKIEJAR,$cookie_file);
    curl_setopt($ch,CURLOPT_URL,$renren_login);
    curl_setopt($ch,CURLOPT_POST,TRUE);
    curl_setopt($ch,CURLOPT_FOLLOWLOCATION,TRUE);
    curl_setopt($ch,CURLOPT_POSTFIELDS,'email='.$renren_username.'&password='.$renren_password.'&login=登录');
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);
    #curl_setopt($ch,CURLOPT_VERBOSE,TRUE);
    $str = curl_exec($ch);
    curl_close($ch);
    $pattern = '/action="([^"]*)"/';
    preg_match($pattern,$str,$matches);
 
 
    $ch = curl_init($matches[1]);
    curl_setopt($ch,CURLOPT_POST,TRUE);
    curl_setopt($ch,CURLOPT_POSTFIELDS,$post);
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);
    curl_setopt($ch,CURLOPT_COOKIEFILE,$cookie_file);
    //curl_setopt($ch,CURLOPT_FOLLOWLOCATION,TRUE);
    //curl_setopt($ch,CURLOPT_REFERER,'http://status.renren.com/ajaxproxy.htm');
    $ret = curl_exec($ch);
    curl_close($ch);
}
?>