@
wy315700 @
zhicheng 用 HMAC 代替手写 hash+salt 算法并没有什么明显的安全优势。
用 PBKDF2 代替手写 hash+salt 算法是安全的。
这里举例说明:
假定攻击者已经取得了完整的数据库权限,但是没法看到代码实现。
攻击者想要获取用户 foo 的明文密码,下面分别讨论针对几种种密码存储方式攻击方式:
1. 密码明文存储
直接从数据库读出来,最不安全,大家都知道。
2. 不加盐 hash
攻击者知道 hash。但是不知道用什么哈希算法生成的 hash,可能是 md5(password),可能是 md5(sha1(password))。攻击者可以利用已知的彩虹表查出 hash 可能对应的明文,分别去尝试登录。如果不是复杂的哈希算法,都有对应的现成彩虹表可查。
3. HMAC
攻击者知道 hash 和 salt。接下来和 2 是类似的,只不过哈希算法在 2 的基础上套了一层 HMAC,变成了这样 hash = HMAC(originalHash, salt)。因为 salt 是随机的,需要自己针对这个 salt 生成彩虹表,没法利用现成的彩虹表。
4. 加盐 hash
攻击者知道 hash 和 salt。和 3 类似,区别就是,在 3 里 hash = HMAC(originalHash, salt),在这里 hash = myFunc(originalHash, salt),攻击者需要想办法知道 myFunc 是什么,这个在不看代码的情况下是很难推出的。
5. PBKDF2
攻击者知道 hash 和 salt。同上,这次换成 hash = PBKDF2(password, salt)。注意 PBKDF2 算法的第一个参数直接就是 password,不需要先计算 originalHash。与 3 相比,攻击者无需推算 originalHash 是怎么得出的,可以直接计算针对此 salt 的彩虹表。但关键的地方来了,PBKDF2 是一个非常慢的算法,这个算法内部会进行多次 HMAC 的迭代,如果指定的迭代次数足够大,通常计算一次哈希值需要 500ms 以上,这时计算足够大的彩虹表是非常困难的。
对上面各种方式的安全性从低到高来个排序,大概是这样:
1<2<3=4<5
3 的难点在于 HMAC 算法通常也不是很快,计算彩虹表成本较高,但是只要有足够时间和设备还是可以计算出来的。4 的难点在于没法知道 myFunc 是什么,攻击具有不确定性,但通常低水平开发者写出的 myFunc 很简单,比如 md5($originalHash . $salt) 这样,这是非常容易猜出来的。所以上面给 3 和 4 画了个等号,两种方式可比性不强,安全性相当。
PBKDF2 算法是可以指定迭代次数的,只要有需要,完全可以把每个哈希值的计算时间调到 10s 以上,大大增加了彩虹表的计算难度。