实用密码学工具——KDF

实用密码学工具——KDF

最近看到有人问,

密码加密与登录验证的原理是怎样的?

有些人说,一边储存一下密码,另一边把密码发过去,比对一下就好了嘛!我相信很多人依然这么想当然。

值得庆幸的是,这个提问者没有这么想,他问道:「用户登录时验证,是对输入的密码从新加密一次,然后对比数据库已加密的字符是否一致来判断是否放行吗? 」而且,他对这个想法依然觉得似乎哪里有问题,所以才来提问。

我本来只是在下面留言,让他看KDF去。他表示中文资料少,那就帮忙帮到底,顺便在这个系列里面专门说一下KDF是什么,它又是怎么解决密码验证的问题的。

在登录密码这个典型案例中,我们都知道,密码是双方共享的,密码如果在一端明文储存,则一旦这一端被攻击,另一端的秘密也就同时被泄漏了。特别是Unix这种一切基于文件的系统,密码就直接写在了/etc/passwd文件中,那么,一旦/etc/passwd被读出,秘密就不存在了。实际上在现代Unix系统中,密码早就不是明文储存,而是经过hash函数处理,保存在/etc/shadow中。这就意味着,攻击者如果拿到了shadow文件,知道了密码的hash,他依然无法知道密码,依然无法在不改变验证逻辑的前提下通过验证。

不要觉得获得密码储存很难,就可以放心大胆的使用明文密码,历史会不断地打脸。国内知名技术论坛CSDN曾经就爆过丑闻,就是储存的明文密码被大规模泄漏。相关新闻至今都能非常方便搜索到(CSDN详解600万用户密码泄露始末:暂关闭登录)。

然而,hash函数是用来做「向指定大小的空间映射的一个工具」。不管密码有多长,最终都被映射到了一个固定大小的空间中。由于映射是一一对应的,如果来源空间远远小于目标空间,那么实际会出现在目标空间的,只会是很小一部分值。比如说原空间有10种可能,目标空间有100种可能,那么根据hash函数的特性,目标空间最多只可能出现10种可能——分别一一同原空间对应——而不是原来期望的100种可能。由于人能记住的密码往往很短,单纯地用一个hash函数处理,出现的可能性也只有不多的几种。攻击者可以不断收集人类产生的密码,计算它的hash,从而建立起hash到密码的反向查询表(彩虹表)。

所以,单纯地对密码做一次hash也没有那么安全。于是有人想当然的觉得:一次不行就两次!

呵呵,两次三次什么,对于彩虹表来说,根本不算什么事嘛!根本的原因在于,输入的密码复杂度过低,来源空间的可能性太小。解决的办法很简单,加点料(salt)就是了。

密码虽然复杂度不高,但是加的salt可以随机生成,只要最终计算hash时,使用salt和密码的结合,即可充分利用目标空间。这样,彩虹表就失效了,因为salt的可能性非常多。理论上,salt的空间需要大于hash空间,才能有效覆盖目标空间。当然,这个salt自然是需要明文读取的。

一旦验证端数据库被攻破,salt很可能也就知道了,虽然彩虹表没有效了,但是由于密码本身复杂度不高,攻击者依然可以暴力破解密码。为了增加攻击者暴力破解的难度,hash函数被设计成很占用资源的方式。比如说PBKDF2常用的设置里有成千上万次迭代,来消耗CPU资源,但是被最近的GPU计算,FPGA计算大大地削弱了防护能力。Linux系统密码采用的bcrypt,Litecoin使用的scrypt则消耗大量内存资源,可以用来对抗GPU计算。而最新的Argon2,则可以同时消耗大量内存和大量CPU。

说道这里,KDF(Key Derivation Function)基本也就说完了:

  • KDF是hash函数,通常用来将短密码变成长密码
  • KDF是密码学安全的,详见(实用密码学工具——Hash
  • KDF需要加salt,用于防彩虹表,salt长度至少要大于hash长度
  • KDF需要有能力消耗大量计算资源,用于防暴力破解
编辑于 2017-01-02 20:29