翻译自:http://pajhome.org.uk/crypt/md5/auth.html
Challenge Response
登录系统的核心是“challenge response”的交换。服务器生成一个随机数(“challenge”)并发送到客户端。客户端根据challenge和密码进行hash操作,并把结果发送回服务器。服务器根据自己的计算检查结果。在交换过程中,密码不作为明文传送。由于每次的challenge不同,避免了重放攻击。
登录表单由两个部分组成:
1.由服务器生成的是16进制表示的128位随机数,存放在名为“challenge”的隐藏域中。
2.在onsubmit代码中:password.value = hex_hmac_md5(password.value, challenge.value)
当服务器收到表单数据后,使用其保存的密码来计算hex_hmac_md5(password, challenge),并与客户端传送过来的进行比较,如果一致则登录成功。
很重要的一点是服务器必须记录它生成的challenges,并只接受那些用来登录的challenges。当一个challenge被用于登录后,它必须设成无效。实际的系统需要一个机制来周期性的使得生成但是没有使用的challenges过期。
Tracking the Login Session
一旦登录发生,服务器必须跟踪用户的session。推荐使用cookie中的session ID。如果这样的话,session cookie可以被嗅探到并重放。然而这样的影响比使用密码要小得多,因为session ID是时间受限的。理论上可以通过对会话中的每一次请求使用challenge-response来避免这个问题,然而太复杂以至于无法正常的编码实现。所以可以接收被嗅探到的风险;如果不能接收的话,可以使用HTTPS。
Transferring the Password Initially
基于challenge response的登录假定服务器已经知道密码。因为当传送最初的密码时,需要使用其它的方法。可以通过JavaScript RSA来实现,推荐使用512位的密钥。
Protecting the Password on the Server
在服务器保存明文的密码是有很大风险的,考虑如果数据库被攻破的情况。因为用户通常在不同的系统之间重用密码,攻击者可以使用密码去访问其它的系统。很多企业的密码策略禁止保存明文密码。
通常的解决办法是password hashing。例如,Unix的密码文件通常使用基于MD5的算法来加密密码。该算法包含一个称为“salt”的数字,该数字对每个用户都是随机生成的。“salt”并不是保密的。它的作用是同样的密码可以加密成不同的hashes。
这种方法可以应用到基于challenge response的登录系统。
1. 服务器发送(challenge, salt)
2. 客户端发送hex_hmac_md5(hex_hmac_md5(password, salt), challenge)
使用这种方法,服务器保存salt和hex_hmac_md5(password, salt),并不保存明文的密码。实际上服务器保存的是“password equivalent”的,即你知道这些值的话,就可以登录。不过,这样的保护是值得的,因为它的主要目的是防止攻击者在其它系统上使用其捕获的密码。
对每个用户使用不同的salt带来一个问题:当用户名未知时salt也是未知的。对于一个web应用程序来说,这需要一个两阶段的登录表单,一个表单用来获取用户名,另外一个用来获取密码。这样的方式对用户是相当不友好的。一个简单的解决办法是:salt是由用户名和一个system salt连接而成的。system salt对所有用户都是一样的。
Brute Force Attacks
暴力攻击是指攻击者对一个用户使用不同的密码,直到最终成功。使用基于challenge-respons的登录方式带来一个新的风险。如果攻击者捕获了登录会话,他们就可以进行离线的暴力破解,不需要访问服务器就能检查密码。这样就避开了很多服务器的限制,比如3次不正确输入密码就锁住账号。
主要的解决办法就是使用强密码。如果用户使用长的随机密码,则暴力破解需要更多的计算能力。另外一个增加攻击者计算能力的方法就是重复使用MD5操作,攻击者就需要对每个密码重复同样的事情。
In Case JavaScript is Disabled
JavaScript不是总启用的。这种情况下,加密的登录是不可能的。有两种选择:要么阻止登录,要么以不加密的方式继续。这是一个安全性和可用性的tradeoff。不同的站点有不同的选择。
为了允许不加密的登录,需要对登录表单进行修改。增加一个password_hash的隐藏域,用JavaScript来填写其值,并使得密码域为空。如果服务器收到一个为空的password_hash,则说明JavaScript被禁用了,服务器可以检查密码域。
阻止不加密的登录有点难度。可以用JavaScript来生成登录表单的HTML,如果JavaScript被禁用的话,该表单不出现。另外一种方式是把表单的target设成about:blank,然后在JavaScript OnSubmit中设置。