日期:2012-02-17  浏览次数:20979 次


  读者总是问我怎样才能在他们的站点上通过使用JavaScript来保护用户的登陆口令,我的第一个反应就是告诉他们使用安全套接层(Secure Sockets Layer,SSL)。当使用正确时,SSL是安全敏感的Web程序最好的解决方法。然而,一些开发者在发布对安全不那么敏感的Web程序时更愿意让用户登陆时不使用SSL。

  Web程序使用一种称为会话状态管理(session state management)的技术来跟踪浏览器与服务器之间的交互,正如超文本传输协议所规定的那样,每个浏览器的请求都独立于其他浏览器,一个Web程序必须使用一些技巧,如cookie,隐藏的表格字段,或URL重写,来识别其与特定的浏览器的特定的会话。大多数服务器端开发环境如ASP,PHP,ColdFusion等使用cookie。

  使用会话状态管理的问题是从根本上说它是不安全的,一个黑客可以在服务器和用户浏览器之间截取用来管理会话状态的cookie,表格字段值,或是URL,一旦得手,他就可以利用这些信息来接管用户的会话!(译者注:典型的例子如冒充交易用户竞价)

  大多数服务器端的脚本开发环境允许你减少这种危险,例如你可以指定cookie的极短失效时间,使用难预测的会话状态信息。然而最安全的还是使用安全套接层(SSL),购买它后,你就可以不仅保护用户口令,还可以保护会话状态管理信息。

  如果你不使用SSL,让用户在你的程序中的安全敏感页面中每次都重新认证是个好主意,但是从用户角度出发,这实在是太麻烦了。最后,你必须考虑你和你的用户究竟愿意承担多大的风险。如果密码口令被曝光的危险很高,还是用SSL来构建你的程序吧,如果你不能使用SSL,那么就使用基于MD5的登陆过程。这至少能保护你的用户的口令。此外,选择一种允许保护会话状态信息的服务器端脚本技术。

  通常,当用户不使用SSL(即普通HTTP)登陆时,他(她)的口令从离开浏览器起至到达服务器为止始终是以明文状态暴露的。

  然而,利用一种称为单向函数的数学函数的帮助,我们可以设计一种并不暴露用户口令的登陆计划。一个函数是一种将集合A的元素映射到集合B的方法,每个集合A中的元素都对应于集合B中的一个确定元素。而一个单向函数的作用就是使逆向的推算非常困难,即,给定集合B的一个元素,很难从集合A中决定哪个元素(如果存在的话)是映射到B集合中的这个元素的。

  一个很好的类比是碎纸机,把文件放到碎纸机中切碎很方便,而从碎片中重建原文件就相当困难了。

< Adelman Shamir, Rivest是著名的RSA加密算法的三位发明人:Rivest, Rivest(Ronald Digest的缩写,MD5是一种128位的哈希算法),由Ronald  一个非常流行且被广泛使用的单向函数应用是MD5算法(译者注:MD是Message>

  我们现在就来利用MD5算法开发一个过程,它将能够保护从浏览器传送到服务器的口令。

  用户试图登陆到一个Web应用程序时,服务器端脚本提供用户一个包含有从数十亿可能值中产生的随机数的表格,当用户在此登陆表格中输入她们的ID和口令,客户端的脚本将这个随机数值添加到口令后并加以MD5单向运算。然后以MD5算法的输出作为口令,我将称这个口令为MD5口令。

  客户端脚本然后将用户ID和MD5口令传输至服务器,因为这个值是MD5算法的输出,反向推算出用户原来输入的口令是几乎不可能的。(译者注:MD5算法已有被德国解密专家攻破的纪录,在对安全要求很高的场合使用仍存在风险)

  当用户ID和MD5算法得出的口令被Web应用程序接收到后,Web程序执行与用户浏览器相同的操作,它将当初传给用户的随机数加到用户口令后,(此口令取自服务器中被保护区域)然后计算出正确的MD5口令值,将此数值与从浏览器收到的值作一比较,如相等,则设置一个服务器端会话变量以确定用户认证通过。

  此时你可能会疑惑为什么要使用随机数,随机数是用来防止重新攻击的,如果仅用户口令经过MD5运算,此MD5口令将始终保持一个值,监听的黑客只需简单地截获这个MD5口令就可用它来登陆进Web应用程序。这个随机数保证了每次新的登陆都有一个特定的MD5口令。

  基于MD5的登陆方案用JavaScript来实现相对容易,一个开放源码的完全实现可从Paul Johnston处得到。(译者注:见附录)

  我们现在用ASP来实现服务器端脚本(因为它支持JScript,MicroSoft版本的JavaScript)。你当然可以用其他任何服务器端脚本,只不过你得将MD5算法翻译成那种语言。
  以下是登陆表格的ASP实现,login.asp:

$#@60;%@ LANGUAGE = "JScript" %$#@62;
$#@60;HTML$#@62;
$#@60;HEAD$#@62;
$#@60;TITLE$#@62;Please log in!$#@60;/TITLE$#@62;
$#@60;% Session("sharedValue") = Math.random().toString() %$#@62;
$#@60;SCRIPT LANGUAGE="JavaScript" SRC="md5.js"$#@62;$#@60;/SCRIPT$#@62;
$#@60;SCRIPT LANGUAGE="JavaScript"$#@62;

var sharedValue = "$#@60;% =Session("sharedValue") %$#@62;"

function handleLogin() {
sendMD5Value(calculateMD5Value())
}

function calculateMD5Value() {
var pw = document.forms["login"].elements["password"].value
pw += sharedValue
return calcMD5(pw)
}

function sendMD5Value(hash) {
document.forms["login"].elements["password"].value = hash
document.forms["login"].submit()
}

$#@60;/SCRIPT$#@62;
$#@60;/HEAD$#@62;
$#@60;BODY$#@62;

$#@60;FORM NAME="login" METHOD="POST" ACTION="checkpassword.asp"$#@62;
User ID: $#@60;INPUT TYPE="TEXT" NAME="userid" SIZE="40"$#@62;$#@60;BR$#@62;
Password: $#@60;INPUT TYPE="PASSWORD" NAME="password" SIZE="40"$#@62;$#@60;BR$#@62;
$#@60;INPUT TYPE="BUTTON" NAME="startLogin" VALUE="Login" $#@62;
$#@60;/FORM$#@62;

$#@60;/BODY$#@62;
$#@60;/HTML$#@62;

以上只有三行是ASP脚本(ASP脚本由$#@60;% 和%$#@62;括起) 。

在服务器端一个名为checkpassword.asp的脚本验证用户ID和MD5口令:

$#@60;%@ LANGUAGE = "JScript" %$#@62;
$#@60;!--#include file ="md5.inc"--$#@6