在现代密码学中,有一种hash函数,又称散列函数,可以将输入的信息,无论多少位,经过一系列的复杂运算后产生固定位数的输出。比如我们熟知的MD5、SHA-1都是如此。
这类函数的特征有:
1、输入和输出是一种多对一的映射关系;
2、由输出一般很难直接推出某一个输入;
3、输入发生微小变化,一般来说可以导致输出发生巨大的变化。
我们姑且认为,对于任何一个特定的,与其他不同的信息(这里记作M)来说,hash(M)具有代表M的摘要的能力。但是也有一定几率,可以找到一个M',使得hash(M')==hash(M)(这种条件下称为“一个碰撞”),但因为几率很小,所以决定了这种认识的可靠性。
我们考虑一下产生上一段所说的一个碰撞的情况的几率。
这里要谈到数学上的一个生日问题,即,在一间屋子里面的n个人,找到一对同月同日生的人的概率P是多少(假设出生的概率在N=366天上均匀分布)?
生日问题的回答:想让P>50%,只需要让n>1.17*sqrt(N)。对于一年来说,n是22.38,即如果一间屋子里有23个人,就有超过50%的几率可以找到一对同月同日生的人。
另一种对此结果的近似(p为想要的概率,d为总的结果空间大小):
现在我们考虑hash函数的碰撞概率问题。hash函数的输出可以假设理想情况,即对于不同的输入,每一位的输出都是在整个空间上均匀分布的。然后,生日问题就变为,在n条信息中,找到一对经过hash后得到同样结果的信息的概率P是多少?结果空间(全部可能结果的空间)的大小为2^N,其中N为输出的位数。
根据上面的结论,我们如果想要让P>50%,需要n>1.17*2^(N-1)。
对于一个输出32位的hash函数,根据上面的近似式,取d=2^32,p=0.99,需要的次数为198893。
下面来重点:
如果我们有一个文件放在网上希望别人下载,但是希望用户在下载之前至少认真地做了一点付出,以免遭到大量的故意请求使服务器挂掉,可以怎么办呢?我们可以做一个验证码,你必须回答这个问题,才能下载。但这并不是适合任何情况的解决方案。比如电子邮件,我们就没法让用户输入验证码,因为我们没有一个服务器可以实现这种集中的管理。
这种情况,我们需要一个机制,让用户在经过这类程序时,本身就需要花费一定的时间。但是当我们验证一个用户是否花费了时间时,却应该是很简单的。
hashcash,是利用hash函数解决上面这种问题的方案。hashcash是一种字符串,我们要求,这个字符串自己的hash值,满足前N位都是0的条件。
这种字符串还必须满足自己具有一定的结构,包含一些信息的条件。根据这些信息,我们实际上是对寻求hashcash的用户提出了附加的要求。一般来说,可能有这些信息是必须包含的:
1、产生这个hashcash字符串时,设计上的 其前导零具有的最少位数;
2、产生这个hashcash的时刻信息。这可以是一个UNIX时间戳。这样,太古老的hashcash字符串我们是不能用的;
3、要求这个hashcash的资源的标识。比如我们要给
abc@XXXXXXXX发邮件,可以让资源写作
abc@XXXXXXXX。这样用在不是这个地址上的hashcash就认为是无效的。
4、自定义的字符串。这让一个用户可以产生满足1-3条件时多个不同的hashcash。
5、调整字符串。仅仅具有1-4的信息是不可能永远产生满足hashcash最基本的特征:SHA1(hashcash)=='00000abcd....'这样的条件的字符串的。这时我们要在这里进行调整,使得特征得到满足。
设计和调整一个字符串,使得1-5的条件得到满足,亦即,花在产生合适的调整字符串,使得整个字符串满足hashcash条件的时间,可以如下估计。
取设计前导零位数20,即,生日问题中的d=2^20,求平均时间,即取p=0.5,我们需要大约1.17*2^19=613417次计算,大约不到100万次。这些计算需要的时间需要根据SHA-1算法的实现和计算机的性能估计。在常见的PC机上,这个时间只需要零点几秒。在手机等设备上,最多需要几秒。
举一个例子:
1:20:111125:lucifer@XXXXXXXXXXXXXXXX::vs203+MO6PahaStZ:00000000000000000000000000000000000000KLE我们可以要求登录时用户需要提供类似上面的hashcash,使其资源为用户名+网站名。在服务器上,我们接受用户登录请求时,首先验证:
1、版本(1),这个目前没用。
2、位数(20),这是一个20位的hashcash,如果少于20,直接拒绝。可以类比为“面值”。
3、生产日期(111125),这是产生在2011-11-25的,如果太旧了,我们可以直接拒绝。
4、使用条件(
lucifer@XXXXXXXXXXXXXXXX),是lucifer用于登录XXXXXXXXXXXXXXXX的,如果登录名这时候写得是别的用户,拒绝。首先查看以上信息通过了,我们再进行真伪验证,计算SHA为:000004f7108836438de1a19fb281a7faff944eb3(十六进制表示),前20个二进制位为0,通过。
我们这时候可以相信,用户已经花费了零点几秒用于登录,这个时间不会少的。用户在一天之内,只能进行这样的尝试不到10万次(这是对于要求20位的hashcash说的,升高位数要求可以很容易地减少这个次数。这个次数是上限,绝不会多于这个次数)。
然而不要太高兴。hashcash使用一次作废。我们要记住见过的hashcash,这需要一个数据库。如果数据库中没有记录了这个hashcash,用户就可以通过了,否则,还要拒绝。数据库只需记录那些生产日期在有效期之内的hashcash,因为过期的在之前的检验中就不会通过,以后也不再会通过。
到此为止,我们已经知道如何阻塞大量获取资源的基本方法了。