后端面试之系统设计 - 用户密码如何储存在DB里
本篇博客的视频教程首发于 Youtube:科技小飞哥,加入 电报粉丝群 获得最新视频更新和问题解答。
背景
现在很多网站都因为爆库导致密码泄漏,要设计怎么样的密码储存机制,才能保证最大限度的不被盗取,即使数据泄漏,黑客也无法在短时间内获取对应的密码来登录用户的账号,而造成损失。
这篇博客就来讲解密码储存的过程。
明文储存密码
username | password |
---|---|
aaa | 123456 |
bbb | qwerty |
ccc | password |
ddd | 111111 |
假设我们有一张用户表,储存username
和password
,密码我们是明文储存的。为什么这么不安全呢?
- 如果遭遇数据泄漏事件,明文密码将用户隐私完全暴露,任何人都可以登录暴露密码的账号,随意更改。
- 密码容易被网站的内部人员得知并获取;也就是内部员工也可以轻易访问用户的明文密码。可能会做一些违法的事情,所以网站基本不会明文储存密码。
当然也有例外,看下这个条新闻:
Hundreds of millions of Facebook users had their account passwords stored in plain text and searchable by thousands of Facebook employees — in some cases going back to 2012, KrebsOnSecurity has learned. Facebook says an ongoing investigation has so far found no indication that employees have abused access to this data.
Facebook 明文存储用户密码,大约有2亿~6亿的用户受到影响,预计超过2万名Facebook员工可检索这些村文本密码 Facebook Stored Hundreds of Millions of User Passwords in Plain Text for Years
所以说,明文储存密码是很不安全的,正常来说现在没有人会这么用。(以前经常还会爆出来)
用对称加密或者非对称加密函数加密储存密码
我想大部分人都了解对称加密和非对称加密,那么用这两种加密算法可不可行呢?
对称加密
在对称加密算法中,数据发信方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。
密钥需要安全储存,否则就有严重的泄漏风险。
非对称加密
非对称加密是一份明文,经过公钥加密成密文,然后使用私钥解密成明文。
公钥和私钥不同,公钥可以公开,这样外部(客户端)可以使用公钥加密成密文,私钥必须保密,这样内部(服务器)才能使用私钥解密成明文。
用这种方式加密密码,能够降低黑客获取明文密码的概率。但密钥一旦泄露,用户的明文密码也就泄露了,不是一个好方法。
而且因为密钥在公司内部,不免有内部员工有不良动机导致账号信息被盗。
所以密码的储存一般不使用对称加密或者非对称加密。
那我们怎么做呢?
用MD5等Hash函数进行密码加密
首先解释一下:Hash 算法是一种消息摘要算法,不是一种加密算法,但由于其单向运算,具有一定的不可逆性,成为加密算法中的一个构成部分。所以为了通俗易懂,我们在本文中可以把MD5等Hash算法理解为加密算法。
MD5是一种Hash算法,也是最常用的一种消息摘要和数字签名算法,常常用于保存密码以及生成数字签名。
MD5 算法具有以下特点:
- 压缩性:任意长度的数据,算出的 MD5 值长度都是固定的(128 bit)。
- 易计算:从原数据计算出 MD5 值很容易。
- 抗修改:对原数据进行任何改动,哪怕只修改 1 个字节,所得到的 MD5 值都有很大区别。
- 抗碰撞:已知原数据和其 MD5 值,想找到一个具有相同 MD5 值的数据(即伪造数据)是非常困难的(极小碰撞概率)。
实际上很多网站都是使用MD5
,SHA-1
,SHA-256
等Hash算法及其变种来进行加密的。
MD5加密的优势:
- 网站被攻破后,无法直接看到明文密码,相对安全性更高;
- 即使是网站的内部人员也无法通过数据库里面的密文得到明文;
但是MD5就安全了吗?
不是的!
虽然无法可以直接获取明文,但可以通过穷举,列表查询方式获取明文,md5的查询库是非常丰富的。
下面我列出三种常用的解密方案。
- 穷举法
- 字典法攻击
- 彩虹表攻击
也有不少网站可以直接进行 MD5 解密。就是用上述三种方法中之一。
穷举法
顾名思义就是穷举所有的明文生成MD5码,然后用MD5码跟泄漏的MD5对比。
比如我上面列出的这个123456
的MD5的值E10ADC3949BA59ABBE56E057F20F883E
,虽然我没办法从MD5推出它是的原文是123456,但是我可以使用很多方法来得到它的原文123456
。
第一步就是穷举法
比如我们穷举6位数数字。
- 计算000000的MD5值670B14728AD9902AECBA32E22FA4F6BD,不匹配,下一个。
- 计算000001的MD5值。
- 计算123456的MD值E10ADC3949BA59ABBE56E057F20F883E,匹配,原文为MD5。
由于现代计算机硬件的发展,CPU每秒钟能进行上百亿次的计算,所以在给定范围内的密码使用穷举法是可行的。
但是如果密码可以使用字母+数字+特殊符号,那么8位数的密码就有数百万亿个组合,16位呢?这个计算更加量庞大。
这种只适合在明文很小的情况下适用。比如6位数字密码
。
字典攻击法
固定长度的密码是有限集,所以生成的MD5值也是有限集。如果你的密码长度很小。那么完全可以生成一个字典表
来做一个明文和密文的映射。拿到这个密文之后匹配明文就可以得到你的密码。
字典表就是提前构建一个 明文 ⇨ 密文 对应关系的一个大型数据库,破解时通过密文直接反查明文。但存储一个这样的数据库,空间成本是惊人的。
密码长度越大这个有限集越大,需要生成的字典表
就越大,匹配明文的难度也就越大。所以记得设置密码的时候设置的长一点,同时多用点特殊字符。这样破解的难度会变大。你知道为什么了吧。
字典攻击法很简单,但是它的限制就是:面对储了大量密码的系统时会非常乏力(储存用于逆向查找的所有选项以及搜索大型数据库十分困难)。
彩虹表攻击
虽然在一定长度内的密码是有限集,但是长度增加的时候那个集合所占用的空间也是不可估量的。
而彩虹表是在字典法的基础上改进,以时间换空间。是现在破解哈希函数常用的办法。
彩虹表是用于加密散列函数逆运算的预先计算好的表,常用于破解加密过的密码散列。彩虹表常常用于破解长度固定且包含的字符范围固定的密码(如信用卡、数字等)。这是以空间换时间的典型实践,比暴力破解(Brute-force attack)用的时间少,空间更多;但与储存密码空间中的每一个密码及其对应的哈希值(Hash)实现的查找表相比,其花费的时间更多,空间更少。使用加盐的密钥派生函数可以使这种攻击难以实现。
彩虹表的核心就是
- H函数,也就是Hash函数,通过明文得到密文。
- R函数,归约函数,通过密文得到一个指定长度的明文。
核心:时间换空间。
有兴趣的小伙伴可以看彩虹表的Wikipedia。 wikipedia
黑客可以使用彩虹表将用户密码复原出,网站常用的密码一般也就 6~16 位,这个取值空间还是很好破解的。
彩虹表(Rainbow Table)是一种破解哈希算法的技术,它的性能非常让人震惊,在一台普通PC上辅以NVidia CUDA技术,对于NTLM算法可以达到最高每秒103,820,000,000次明文尝试(超过一千亿次),对于广泛使用的MD5也接近一千亿次。更神奇的是,彩虹表技术并非针对某种哈希算法的漏洞进行***,而是类似暴力破解,对于任何哈希算法都有效。
MD5加盐(Salt)进行密码储存
与普通的MD5不同,把MD5(password+salt)
进行MD5得到的密文储存在系统中,密文和Salt分别储存。
username | password | salt |
---|---|---|
aaa | 90005CB0DAE7C8BE36B3AD50FA638928 | abcdefghij |
bbb | 471AE89F04C9B18B276FD7571D5402FA | qweryuiop |
根据安全级别,salt可以设置为固定值或者每一个用户不同的值,这个值一般为用户数据行的某一个固定值且对用户不可见的值,千万不要用username作为salt。这种如果有人知道了加salt的算法,那就跟普通的MD5没啥区别了。
每个salt都是独一无二的,随机生成的字符串,在哈希过程中添加到每个密码中。由于salt对于每个用户而言都是唯一的,因此,即使两个用户的密码是一样的123456
,因为每个用户都有自己独立的 salt,所以最终储存在DB里面的password
也是不一样的。
预防彩虹表攻击
salt还可以防止攻击者使用预先计算的彩虹表。最后,使用salt意味着无法在不破解哈希的情况下确定两个用户是否具有相同的密码,因为即使密码相同,不同的salt也会导致不同的哈希结果。
当然随着计算机硬件的飞速发展,现代CPU的运算速度越来越快,这种破解只会越来越容易。
密码验证流程
用户使用电脑输入账号密码(username+pwd),发送HTTPS网络请求,服务器收到请求后根据username查询数据库,匹配到一条数据,然后使用pwd和数据库里的独有的salt 进行MD5运算,得到的结果和数据库里面的password进行对比,匹配则密码正确,返回登录成功。
不匹配则密码错误,返回登录失败。
这个流程还有一个问题,虽然我们数据库里面储存的数据已经是MD5进行Hash之后的字符串,但是接收到的HTTPS的网络请求的pwd还是明文的,这样服务端的人员或者黑客也一样有机会能截取到密码明文,同样有安全隐患。
一般在发送网络请求之前,客户端/浏览器也需要对密码进行加密,一般来说客户端会用类似的MD5哈希算法。 可以使用多层Hash。
pwd = sha256(md5("123456")) //对密码进行md5然后sha256
这样通过网络发送的pwd
就是加密之后的字符串。就有更强的安全性。
MD5+salt其实是很多互联网公司的比较普遍的密码加密算法了。基本上目前大部分互联网公司都是使用的这种加密,可以抵挡大部分的数据泄漏。
四、Bcrypt让你的密码更安全
即使是加了盐,密码仍有可能被暴力破解。因此,我们可以采取更「慢一点」的算法,让黑客破解密码付出更大的代价,甚至迫使他们放弃。提升密码存储安全的利器Bcrypt,应运而生。
bcrypt 是基于 eksblowfish 算法设计的加密哈希函数,它最大的特点是:可以动态调整工作因子(迭代次数)来调整计算速度,因此就算以后计算机能力不断增加,它仍然可以抵抗暴力攻击。
相对于MD5,Bcrypt加密算法的特点:
- 相同明文通过Bcrypt生成的密文每次都是不一样的,MD5则相同。这样就无法通过直接比对密文来反推明文。
- Bcrypt是种慢哈希算法,执行时间较长。有文章指出,针对某一字符串,Bcrypt执行一次加密约0.3秒,MD5加密约1微秒(百万分之一秒)。使得暴力破解Bcrypt的时间成本很高。
- Bcrypt加密长度60位,MD5是32位,提高穷举难度。
如图所示:
我们明文字符串123456
经过10轮加密之后的密文如下:
其中:
2a
是Brcypt加密的版本号,有 2,2a,2b,2y等,有些因为安全原因已经废弃不用了。Rounds
是一个可选的随机数,默认为12,取值[4,31],同一个字符串由于Rounds不同得到的密文也会不同。Salt
加盐字符串,如果未指定,则会自动生成一个加盐字符。Hash
生成的Hash值 。
那如果黑客使用彩虹表进行hash碰撞呢?
BCrypt
算法最大的特点是我们可以通过参数设置重复计算的次数,重复计算的次数越多耗时越长。如果计算一个哈希值需要耗时1秒甚至更多,那么黑客们采用暴利法破解密码将几乎不再可能。破解一个6位纯数字密码需要耗时11.5天,更不要说高安全级别的密码了。
Scrypt和PBKDF2 也是类似的,有兴趣的小伙伴可以自己学习下。
结论
- 大部分公司使用MD5+Salt的方式进行密码加密,Salt要每个用户都不一样,可以增大彩虹表攻击的难度。
- 如果有更高的密码安全性要求,可以使用Bcrypt等算法进行密码加密,破解难度更高。
所以你知道为什么你忘记密码之后只能重置密码吗,因为网站也不知道你的密码原文,他们只知道你的密码经过一系列哈希之后的密文,密码校验的时候也是通过同样的哈希函数进行校验是否正确。所以当用户忘记密码的时候,提供的是重置密码而非找回密码。
<全文完>