哈希函数 哈希表 加盐
哈希表
定义
首先我们说什么叫哈希表?为什么叫哈希表?先不要管为什么叫Hash,这个我们在哈希算法的部分讲。
哈希表,又叫散列表,是能够通过给定的关键字的值直接访问到具体对应的值的一个数据结构。也就是说,把关键字映射到一个表中的位置来直接访问记录,以加快访问速度。
其中最典型的两个哈希表就是Map和Set,本质上二者都是通过Key去查找Value的数据结构,只不过对于Set来说,Key和Value的值是相同的而已,这就是他们的区别,他们都属于哈希表这种数据结构。
哈希 & 哈希函数
来源
所谓的哈希,即Hash,其实就是一种杂凑,或者说乱算,它的本质就是通过一个不可逆的单向映射将一个任意长度的输入M映射为一个固定长度但是较短的哈希值,也叫散列值、杂凑值或者消息摘要。
说它是一个加密过程其实并不妥,因为它只能用来加密,没办法用来解密。比如,你把三体三部曲原文用Hash算出来一个较短的Hash值,正向当然可行,但是你绝对没办法直接使用Hash值推算出整套三体,因为原本的信息已经丢失的差不多了。
因此,哈希并不是一种加密,加密对应的一般默认要有解密过程,但是hash没有办法解密。
那么,为什么Hash叫做Hash呢?
我们搜一下这个词:
这个词的本意其实是杂糅,拼凑,杂凑。举个例子,我们把原生的马铃薯擦成丝,然后炸成粉,最后再成型成薯片或者薯条,这个过程我们就说土豆子被“Hash”了,计算机里的Hash也是类似的道理,即我们把原明文给杂糅了,杂糅成了一个结果。我们无法通过薯条去推断出整个土豆子的结构,也无法通过Hash之后的Hash值推断出明文的结构。
特点
- 单向性:从哈希值不能反向推导原始数据(计算不可行),即从哈希输出无法倒推输入的原始数值。这是哈希函数安全性的基础。
- 灵敏性:对输入数据敏感,哪怕只改了一个Bit,得到的哈希值也大不相同。换言之,消息M的任何改变都会导致哈希值H(M)发生改变。
- 易压易算:Hash本质上是把一个大范围集合映射到另一个小范围集合。故输入值的个数必须与小范围相当或者更小,不然冲突就会很多。所以,哈希算法执行效率要高,散列结果要尽量均衡。
- 抗碰撞性:理想Hash函数是无碰撞的,但实际上很难做到这一点。有两种抗碰撞性:一种是弱抗碰撞性,即对于给定的消息,要发现另一个消息,满足在计算上是不可行的;另一种是强抗碰撞性,即对于任意一对不同的消息,使得在计算上也是不可行的。
用途
- 唯一标识或数据检验:能够对输入数据或文件进行校验,判断是否相同或是否被修改。如图片识别,可针对图像二进制流进行摘要后MD5,得到的哈希值作为图片唯一标识;如文件识别,服务器在接受文件上传时,对比两次传送文件的哈希值,若相同则无须再次上传(传统的奇偶校验和CRC校验一定程度上能检测并纠正数据传输中的信道误码,但没有抗数据篡改的能力)。
- 安全加密:对于敏感数据比如密码字段进行MD5或SHA加密传输。哈希算法还可以检验信息的拥有者是否真实。如,用保存密码的哈希值代替保存密码,基本可以杜绝泄密风险。
- 数字签名。由于非对称算法的运算速度较慢,所以在数字签名协议中,单向散列函数扮演了一个重要的角色。对Hash值,又称“数字摘要”进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的。
- 散列函数:是构造散列表的关键。它直接决定了散列冲突的概率和散列表的性质。不过相对哈希算法的其他方面应用,散列函数对散列冲突要求较低,出现冲突时可以通过开放寻址法或链表法解决冲突。对散列值是否能够反向解密要求也不高。反而更加关注的是散列的均匀性,即是否散列值均匀落入槽中以及散列函数执行的快慢也会影响散列表性能。所以散列函数一般比较简单,追求均匀和高效。
- *负载均衡:常用的负载均衡算法有很多,比如轮询、随机、加权轮询。如何实现一个会话粘滞的负载均衡算法呢?可以通过哈希算法,对客户端IP地址或会话SessionID计算哈希值,将取得的哈希值与服务器列表大小进行取模运算,最终得到应该被路由到的服务器编号。这样就可以把同一IP的客户端请求发到同一个后端服务器上。
- *数据分片:比如统计1T的日志文件中“搜索关键词”出现次数该如何解决?我们可以先对日志进行分片,然后采用多机处理,来提高处理速度。从搜索的日志中依次读取搜索关键词,并通过哈希函数计算哈希值,然后再跟n(机器数)取模,最终得到的值就是应该被分到的机器编号。这样相同哈希值的关键词就被分到同一台机器进行处理。每台机器分别计算关键词出现的次数,再进行合并就是最终结果。这也是MapReduce的基本思想。再比如图片识别应用中给每个图片的摘要信息取唯一标识然后构建散列表,如果图库中有大量图片,单机的hash表会过大,超过单机内存容量。这时也可以使用分片思想,准备n台机器,每台机器负责散列表的一部分数据。每次从图库取一个图片,计算唯一标识,然后与机器个数n求余取模,得到的值就是被分配到的机器编号,然后将这个唯一标识和图片路径发往对应机器构建散列表。当进行图片查找时,使用相同的哈希函数对图片摘要信息取唯一标识并对n求余取模操作后,得到的值k,就是当前图片所存储的机器编号,在该机器的散列表中查找该图片即可。实际上海量数据的处理问题,都可以借助这种数据分片思想,突破单机内存、CPU等资源限制。
- *分布式存储:一致性哈希算法解决缓存等分布式系统的扩容、缩容导致大量数据搬移难题。
哈希算法,或者叫,哈希函数,其实有很多实例了,这算是密码学里重要的一个研究方向,比较经典的算法比如SHA1、MD5等等,这些算法的视频讲解可以去看土豆子老师的视频:https://space.bilibili.com/253413704
昨天看了一整天密码学,给我人都看麻了:
我主要在玩网络安全,密码学的这些玩意是我要是用的东西,而不是我要研究的东西,我直接把它当成黑盒子是用就行了,一定要找准自己的研究内容,把无关内容解耦出来。
哈希的构造函数
如何构造一个哈希表,也是一个很严肃的问题。
市面上有很多哈希函数的构造函数实现方法,也不用刻意去记忆,说一个比较简单的,比如存储数字到一个数组中,则将数字x1对一个很大的数M取余数,余数则作为Key,也就是hash值。
但是这会存在一个问题,因为余数是有限的,只有M个,因此会发生冲突,或者说碰撞。
举个例子,比如我的M是10,我一开始存入了3,3的余数是3,因此把3存在了3位置;但是后面我会存入13,13的余数也是3,因此就产生了覆盖的冲突。
这里解决冲突的思路有两种:
开放寻址法
比如线性探测方法,即发生冲突则继续往下找坑,如果往下找还有冲突,就继续往下找坑,每一步先找1的1次方下面的位置,再找2的2次方,3的3次方……直到找到一个没有冲突的位置。
封闭寻址法
比如链表法,即在同一个位置中设置一个链表,即哈希值相同的数据我们都存在这个位置,但是用一个链表穿起来,每次访问的时候,再遍历这个链表。
当然这个缺点在于可能会发生集群现象,即会有几个位置有相当长的链表,那么查找效率就会很低。
加盐
这个词很形象,但是我第一次接触给我整的很懵逼,大概意思我表述如下:
这是哈希算法的一个主要应用之一中可能发生的问题:(假设CSDN使用了加密算法)
比如,我的CSDN密码是123456,你的CSDN密码是123456,那么,正常情况下,我们生成的哈希值是相同的,因此,这就是我破解你密码的一个很重要的优势。
首先,我找到一个常用密码库,然后我选择目标网站的加密算法,并且我黑入了目标网站的数据库,找到了你的Hash值,那么,我就可以写一个简单的循环让程序去这个常用密码库里查找了,如果某一个密码明文生成的Hash值和你的Hash值相同,那就代表我找到了你的密码明文,而你很有可能在其他网站也用了这个密码明文,你就危险了。
如何避免这个问题呢?就是加盐。
说白了就是,你在煎鸡蛋,我也在煎鸡蛋,虽然我们的蛋一样,但是加的盐可能不同,最后的口味也不同。
我们在保存用户密码Hash值之前,不能直接让用户的明文密码去做加密算法计算Hash值,而应该再加入一些随机字符串,到时候我们保存的是这个随机字符串和Hash值,而这时候的Hash值就是用用户的密码明文+随机字符串共同计算的Hash值,从而使得明文密码和密文Hash值没有直接的一对一的关系,可以对攻击产生更困难的阻碍。而这个随机字符串,可以是用户名,当然也可以是用户的任何信息,只要攻击者不知道是什么信息,那在计算量上,上面的方法可以说就完全失效了。
这个随机随机字符串,就是所谓的盐,在密码学里面应该被叫做“盐值”。
参考:
哈希究竟代表什么?哈希表和哈希函数的核心原理
https://www.bilibili.com/video/BV1SZ4y1z7wT/?spm_id_from=333.337.search-card.all.click&vd_source=d0edf1d3f760c41c638551d47a67826e
密码学简单科普2——散列
https://www.bilibili.com/video/BV1xP4y1x7FC/?spm_id_from=333.337.search-card.all.click&vd_source=d0edf1d3f760c41c638551d47a67826e
加盐加密的简单理解
https://blog.csdn.net/weixin_37968613/article/details/93852709
哈希算法(哈希函数)基本
https://blog.csdn.net/suoluo_2020/article/details/120381845
散列表(哈希表)及其存储结构和特点详解
http://data.biancheng.net/view/107.html