来给密码加点“盐”

一步一步带你理解密码学中经常说的“盐”是怎么回事

Posted by Jeremy Song on 2021-07-25
Estimated Reading Time 7 Minutes
Words 2.2k In Total
Viewed Times

今天给大家来点密码学的知识!聊聊密码学中的 “盐” 是怎么回事。

背景

什么是“盐”

Salt),在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。

为什么要加“盐”

通常情况下,当字段经过散列处理(如[MD5]),会生成一段散列值,而散列后的值一般是无法通过特定算法得到原始字段的。但是某些情况,比如一个大型的彩虹表,通过在表中搜索该MD5值,很有可能在极短的时间内找到该散列值对应的真实字段内容。

加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加,插入的字符串扰乱了真正的密码,使得获得真实密码的概率大大降低。

彩虹表

彩虹表是一个用于加密散列函数逆运算的预先计算好的表, 为破解密码的散列值(或称哈希值、微缩图、摘要、指纹、哈希密文)而准备。一般主流的彩虹表都在100G以上。 这样的表常常用于恢复由有限集字符组成的固定长度的纯文本密码。这是空间/时间替换的典型实践, 比每一次尝试都计算哈希的暴力破解处理时间少而储存空间多,但却比简单的对每条输入散列翻查表的破解方式储存空间少而处理时间多。使用加salt的KDF函数可以使这种攻击难以实现。彩虹表是马丁·赫尔曼早期提出的简单算法的应用。

—— 摘自《百度百科》

用例子来说明加“盐”的意义

OK!上面这几段枯燥的文字读完如果你还不知所云,那没关系。咱们通过下面这个例子来说明加 “盐” 的意义在哪里!

原始对照

假设我需要创建一个数据库表来存储用户登录的账号和密码用于系统的登录业务。那么如果我使用MySQL的话,可能会设计一张如下的表来存储用户的账号和密码信息。

uid username password
100 Jeremy 123456
101 Melissa 654321

有了这张表,每次将客户端提交的密码和数据库中的密码做对比,如果一致则表示密码正确,登录放行,反之拒绝登录。

这么一看,好像一切都很完美的样子。直到有一天,因为系统的某个漏洞,黑客盗走了我们这种保存了用户名和密码的表。这下出大事了,我们所有的用户信息完全暴露了,黑客通过这张表里的信息就完全掌握了所有的用户资料。

先不说出了这事的影响有多大了,咱接着看看如何来改进我这个很low的存储方案来避免这个问题。

进阶的设计

上面发生的那种问题是我们无论如何都想要避免的,而且系统的漏洞也是绝对意义上杜绝不了的。那么我们思考一下,即使在表数据泄露的情况下,如何来确保数据相对安全。

这个时候,我们就会想到散列计算,例如:MD5、SHA1、SHA256等等,通过散列计算会使计算出的散列值和原始值不相等。登录验证的时候我只需要比较密码明文的散列值和数据库中的散列值就可以判断用户输入的密码是否正确,同时数据库里的值存储的也不是原始密码。这样就算表数据泄露了也隐藏了原始密码,密码不就安全了嘛。于是我使用MD5的散列计算,将数据库表改成了下面的样子。

uid username password
100 Jeremy E10ADC3949BA59ABBE56E057F20F883E
101 Melissa C33367701511B4F6020EC61DED352059

注:上面password列存储的是经过MD5散列计算得出的32位大写的计算结果

MD5(123456) = E10ADC3949BA59ABBE56E057F20F883E

MD5(654321) = C33367701511B4F6020EC61DED352059

这样,数据库中保存的就不是密码明文了,比第一种方案就安全了很多。

但问题是,这个也只是我们觉得安全了很多。假设这个密码是一个类似银行取款的六位数字,那黑客难道就没办法知道这个MD5的原始明文了吗?我感觉你也猜到了!

没错!如果你创建了一个 000000 - 999999 范围内所有的MD5散列值Map,这玩意一比较不就出来了嘛!然后我们想着我可以改变密码的长度,改变算法来让黑客们必须拥有一个超大的字典才能枚举出来明文,让黑客们解密的成本增加,直到他们放弃。但实际上,这个人家也是有办法的。黑客们可以通过彩虹表来破解我们的密码。

文章一开始从百度百科摘抄了一段彩虹表的解释,如果你还是想知道如何使用彩虹表来破解密码可以查阅下面这篇文章:

扩展阅读:

加“盐”的设计

至此,我们不得不给我们的密码加点 “盐” 了,谁想破解就齁死他。所以这次,我把数据库表改成了这种:

uid username salt password
100 Jeremy 9eb8fd 5830FA2FC5AF55D71363A9956387EE63
101 Melissa 7fb9ek C33367701511B4F6020EC61DED352059

对上表的解读:

  • 上表中的 salt 是一个随机的6位字符串
  • password 的算法变更为 MD5( 密码明文 + salt )
  • MD5(“123456” + “9eb8fd”) = 5830FA2FC5AF55D71363A9956387EE63
  • MD5(“654321” + “7fb9ek”) = D925CC53A219768B128AE52DD2C3AFFD

这样我们就给密码加上了 “盐” 。因为黑客不知道加盐的规则,所以原始密码+盐之后的情况就增加了很多种,这 加“盐” 之后就大大的增加了密码的破译难度。

我们上面的示例是直接在原始明文的末尾加上了 “盐”,但这个不是必须的,你可以在任意位置来加盐,我们以Jeremy这个用户为例(密码明文为123456),使用如下方式加“盐”都是可以的(加粗的字符表示“盐”值)

  • 1234569eb8fd 末尾加盐
  • 9eb8fd123456 头部加盐
  • 1239eb8fd456 任意位置插入盐
  • 129eb4568fd 任意位置分散插入盐
  • 1234df8be956 任意位置倒序加盐

上面的例子就可以看出,一个六位数的盐就可以让一个原始的密码明文产生非常多的变种。当然你还可以对你散列运算函数做变化,比如对加盐的MD5值再加盐再求散列 MD5(MD5(password + salt) + salt)。正因为如此,多种的可能性极大的增加了破译的难度。

实际的情况中,你还可以把这个“盐”值和密码表分开存储,这样则又进一步增加难度,因为黑客不知道这个 “盐” 值到底是个什么字符。从可能性上来讲,不知道这 “盐” 是什么就已经完全无法破解了。

最后

散列值运算的结果其实是无法通过逆运算算法来破解的,因为不同的值经过散列运算可能会得到相同的散列值。就算直到了逆运算算法,我们也还是无法直到具体那个才是明文。散列值破解都是靠字典命中来实现的,彩虹表就是在设计上减少了命中的时间消耗,但是彩虹表还是需要一个预先计算的散列链来支持的。

现代密码学中的破解防御,其实大部分都是基于让破解成本(时间或空间或时间+空间)大大增加而使破解成为不可能的。比如按照目前计算机的算力,算出来需要XXX亿年的这种。但随着量子计算机的问世,原来看似牢不可破的加密算法也受到了挑战。不过我们不也不用过度担心,现在量子计算机还是没有那么成熟,同时密码学也有是一群大神们在研究的。等真的到了那一天,应该会有针对量子计算机的加密算法问世。

顺便多说一句,平时只要我们设置的密码尽量的长一点,大小写字符、数字、符号混用都会大大增加密码的强度。


欢迎关注我的公众号 须弥零一,跟我一起学习IT知识。


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !