Skip to content

密码哈希

目录


本文从低级到高级简单介绍一些开发中存储用户密码的方案

背景

在开发中,我们经常需要存储用户的密码,比如用户在注册时,我们需要将用户的密码存储在数据库中,以便用户在登录时进行验证

这样的用户管理方式导致了一个问题,任何人一旦获取到了用户和密码,就相当于拥有了用户的所有资源

黑客当然不会放过这样的机会

由于直接攻击服务难度太大(比如我很难攻击银行的系统往我账户上添加 50 块钱,但是我可以通过盗取你的账户往我账户里打 50 块钱),并且数据库系统一般是独立的系统(只需要能读取数据库内容就可以,读取一般比修改更容易),所以数据库便成为了重点攻击对象

黑客通常在攻击服务器时,一旦获取到数据库,就可以获取到用户的密码,从而进行下一步的攻击活动(比如 V 我 50)

总之黑客获取到用户密码是容易发生且危险的事情

目标

所以我们需要一种方式,阻断或者延缓黑客获取用户密码的行为

密码存储的方式

这里重点讨论通过更改密码在数据库的存储方式来达到目标

明文存储

最弱的方式,就是直接将用户密码以明文的方式存储在数据库中,这是最不安全的方式

比如:

用户名平台 A 密码
user1123
user2cxxy
user3123

黑客一旦获取到了数据库,便可以以用户的身份登录到平台 A

更糟糕的是,黑客可以利用撞库的方法,来登陆该用户在其他平台的帐号,造成更多的损失

由于用户常常在多个平台使用相同的密码,所以一旦黑客获取到了用户在一个平台的密码,就可以尝试在其他平台使用相同的密码进行登录,这种攻击方式叫做撞库

密钥加密

将用户密码通过密钥加密后存储在数据库中,这种方式比明文存储要安全一些,但是对于攻击了数据库的黑客来说,拿到密钥不是特别难

因为黑客一般是先攻击服务器,然后才拿到数据库的,所以拿到密钥并不难,能拖延的时间也不是很长

比如设计一个简单的算法,将用户的密码每一位加上密钥的对应位的值,然后存储在数据库中

设定密钥为2

用户名用户设定密码加密后密码
user1123345
user2cxxyezz{
user3123345

使用 user1 - 345 这样的密码无法登陆平台 A,但只要知道加密算法和密钥,就可以轻松解密

Hash 加密

将用户密码通过 hash 函数加密后存储在数据库中,这种方式是目前最常见的密码存储方式,这样黑客拿到数据库后,也无法直接获取到用户的密码

hash 加密函数是一种单向的不可逆的加密方式,即无法通过结果的 hash 值反推出原始密码

但是相同的字符串经过相同的 hash 函数加密后得到的结果是相同的

以 md5 加密为例

用户名用户设定密码md5 加密后密码
user1123ac59075b964b0715
user2cxxy3997966d4f5c3d44
user3123ac59075b964b0715

黑客就算获取了数据库,也无法登陆用户的账户,因为无法通过 md5 加密后的密码反推出原始密码

当用户登陆时,我们将用户输入的密码进行 md5 加密,然后和数据库中的 md5 值进行比对,如果相同,则验证通过

但是道高一尺,魔高一丈,黑客也有自己的方法来解决问题

黑客可以解析代表密码的 hash 值,如果有大量的相同的 hash 值,那么代表这个系统可能有一个默认密码,便可以开始尝试常用默认密码(比如123,admin123

在这个案例中,默认密码是123 ,所以黑客可以通过尝试123来登陆用户的账户,虽然user2 的账户安全了,但是user1user3 的账户就有风险了

一般在这样的攻击中,黑客的重点不是破解某个账户的密码,而是获取更多的账户密码,那肯定是挑软柿子捏

在此之上,还有一些更高级的攻击方式:

  • 彩虹表:由于 hash 函数的特点,以及目前 hash 函数就那么几个。黑客便可以提前生成一张常见密码对于的 hash 值表,等获取到数据库之后便可以更快的破解密码
  • 字典攻击:相比于无序的字母组合,用户更倾向于选择含有意思的单词作为密码或作为密码的一部分,黑客可以通过字典中的常见词组合来生成彩虹表,无序字符串的组合比有序字符串的组合要少很多很多很多,字典攻击可以增加破解密码的范围

加盐 Hash

正对彩虹表以及字典攻击,开发者们想到了加盐 hash

通过在密码原文中加入一个随机字符串,然后再进行 hash,这样即使 2 人有相同的密码,由于加入了不同的盐,hash 值也是不同的

比如我们在每个人的密码后面加上一个随机字符串,然后再进行 md5 加密,这样即使两个人的密码相同,由于加入了不同的盐,hash 值也是不同的

用户名用户设定密码md5 加密后密码
user1123114514e07bbfb1f39f0c38
user2cxxy1919810718ab3701df3e708
user31238102e3b58f53ab497c6

彩虹表和字典攻击虽然还能用,但效率相比于之前大大降低了(需要针对每个盐生成彩虹表)

但无论如何 salt(盐) 是需要存储在数据库中的,黑客拿到数据库后,也可以拿到 salt,可以通过暴力的方式来破解密码

由于传统 hash 的设计计算速度很快,加上现在的 GPU 计算能力加快,加盐 hash 能拖延的时间不是很长

BCrypt

在这样的背景下,出现了 BCrypt,它是一种慢 hash 函数,它的设计是为了拖延黑客的破解速度而非简单阻止黑客破解密码,因此它被设计为可以手动调整快慢的 hash 函数

底层原理可以简单理解为将 salt 和密码轮流作为 hash + salt 进行多次加密(用 salt 加密密码,然后用加密后的密码加密 salt,然后再用加密后的 salt 加密密码,然后再用加密后的密码加密的 salt...),并且可以通过设置 cost 参数来调整加密的次数,从而减慢加密的速度

这里加密使用的一般是 Blowfish 算法,与上面的加密算法不同的是,Blowfish 是一种对称加密算法,通过 salt 和加密后的字符串可以还原加密前的字符串

这样即使黑客拿到了数据库,也需要花费大量的时间来破解密码,收益与回报不成正比,倒逼黑客放弃这攻击方式

BCrypt 不需要要额外记录 salt,因为它会自动将 salt 保存在 hash 值中,一个典型的案例如下

$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
\__/\/ \____________________/\_____________________________/
 Alg Cost      Salt                        Hash

这里的 salt 是随机生成的最初的 salt(如果是最终 salt 的话是可以解除原密码的),当用户输入密码时,bcrypt 会从 hash 中提取 salt,然后用这个 salt 和用户输入的密码进行 hash,然后再和 hash 值进行比对

这样的加密方式在包含了上述加密方式的优点情况下,还能拖延黑客的破解速度

在实际情况中,企业也不是白痴,当发现数据库被攻击后,可以选择提醒用户更改密码,或者提前对资源进行保护(比如引入多因素验证),BCrypt 拖延的时间能让企业有更多的时间来应对黑客攻击

不过慢 hash 也是一把双刃剑,因为它的计算速度慢,所以在验证密码时也会比较慢,并会对服务器造成一些压力,所以在设置 cost 时需要权衡利弊


参考资料:

Copyright © 2022 田园幻想乡 浙ICP备2021038778号-1