delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

15 Aug 2006

关于密码验证的一些问题

前几天 hzqbbc 兄 [blog]在blog上留言中提及此事,正好就这个话题说两句。

通常我们在设计安全系统的时候,会希望在满足功能的前提下为每个用户授予最少的权限。对一个数据元而言,假如我们限定允许的权限包含:写、读(获取其内容)、验证(判断给定值是否有很大概率与其相等)、判断其是否存在、完全没有这5类时,通常我们会发现,比较靠前的权限会包含(或暗含)靠后的权限,或者说,靠前的授权要比靠后的授权来的大。(注意,此处我们并不考虑拥有写权限,而没有读权限这种情形,这种授权通常见于文件系统)。

在进行基于口令的身份验证时,通常会给验证用户(邮件系统以这个用户的身份去验证数据库中查找并决定是否允许将访客由「匿名用户」变为某个已经完成了身份验证的用户的权限提升)。一般来说,习惯上会有两种不同的权限授予,第一种是授予「读」权限,即,验证服务能够获得用户的口令。

这种方法的一种比较常见的用法是为了支持challenge-response验证,即,服务器向客户发出一个「挑战码」,客户将密码与这个挑战码进行一些计算(一般而言是hash),并发给服务器。服务器验证这个结果的正确性。这种方法能够在没有启用TLS的前提下避免在网络上以明文方式传递口令,但一般说来,后续的通讯是不进行加密的。

然而,这种方式尽管总体的加密计算量较少,而且无需对数据提供者进行改动,但却存在两个无法回避的缺点。第一,口令必须以明文方式保存在服务器上,一旦服务器被攻陷,不仅用户数据会随之泄密,而且用户的口令也会随之泄密。第二,这种方式将原先只有一个服务(数据库)了解的秘密,转化为了两个服务(数据库服务和验证服务,后者无论是否验证通过都必须将口令复制到自己的进程地址空间),因而增加了容易受攻击的环节。

另一种方法,则是在数据库中不保存口令,反之,只保存口令的hash。这种方式有一个缺点,即密码必须以明文(当然,可以通过加密信道传递)而不是hash传递给服务器。这种方式中,既可以让验证服务拥有对数据库的「读」权限(将hash读过来,然后验证),也可以只给「验证」权限(将口令发给数据库服务,然后取结果)。在LDAP中,后一种方式可以通过「bind」操作直接实现,此外,即使数据库中保存的是明文,只要验证时要求用户将口令发到服务器端,仍然可以通过bind操作来完成验证的工作。

因此,一个适应性强的LDAP验证模块,应具有以读出口令并进行验证,以及通过bind来完成验证这两种能力。而如果上层的服务没有采用challenge-response验证模式,则实现后者的适应性要略好一些。