哈希算法-如何防止隐私信息被「脱裤」
Posted Python七号
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈希算法-如何防止隐私信息被「脱裤」相关的知识,希望对你有一定的参考价值。
阅读本文大概需要 6.6 分钟。
2011 年 csdn 网站被黑客攻击,超过 600 万用户注册的邮箱和密码明文被泄露,这种攻击叫拖库,因为谐音,也经常被称作「脱裤」。黑客拿到这些用户和密码去其他网站登陆,得到一系列可以登录的用户,这种攻击叫撞库。
前不久,网上传出 12306 网站的用户信息被泄露。
可以想象,大部分人都懒于记忆多个密码,索性所有的网站,app 都使用一个密码,更有人使用弱密码,据说弱密码 000000,123456,666666,888888,这四个密码可以破解中国 30% 的银行卡密码。如果用户信息被明文泄露,用户的损失不堪设想,作为企业也将陷入信息安全的舆论漩涡,丢失用户。
假如作为这些公司的程序员,你有什么办法防止数据库的信息被「脱裤」呢?
当然,大家肯定都知道对于用户密码这种敏感信息要加密存放,就算黑客拿到了数据,看到的也是密文,这样就无法推出登录密码,从而避免了撞库。那么用 MD5 加密一下再存储是否就够了吗?要搞清楚这个问题就要学习下哈希算法。
什么是哈希算法?
哈希算法,简单地说就是将任意长度的二进制串映射为固定长度二进制串,这个映射的规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值。哈希算法,哈希表,散列表,hash 表,这些都是一个意思,翻译的不同罢了。
哈希算法要满足以下几点要求:
1、从哈希值不能反推出原始数据。
2、原始数据哪怕只修改了一位,得到的哈希值也大不相同。
3、散列冲突的概率要很小。
4、哈希算法的执行效率要尽量高。
举个例子:
>>> import hashlib
>>> data1 = "12345"
>>> data2 = "12346"
>>> hash_md5_1 = hashlib.md5(data1.encode('utf8'))
>>> hash_md5_2 = hashlib.md5(data2.encode('utf8'))
>>> hash_md5_1.hexdigest()
'827ccb0eea8a706c4c34a16891f84e7b'
>>> hash_md5_2.hexdigest()
'a3590023df66ac92ae35e3316026d17d'
从结果 827ccb0eea8a706c4c34a16891f84e7b 你无法推出 12345,12345 与 12346 就差一位,但是他们的哈希值却大不相同。
哈希算法为什么无法做到 0 冲突?
先说下鸽巢原理(也叫抽屉原理),假如有 10 个鸽巢,有 11 只鸽子,那肯定有一个鸽巢里的鸽子数量多于 1 个。哈希算法也是一样,哈希算法生成的哈希值的位数是固定的,而要哈希的数据是无穷的,因此必然会存在哈希值相同的情况,只要相同的概率足够低就可以了,一般情况下哈希值越长的算法,冲突的概率越低。
不过,即便哈希算法存在散列冲突的情况,但是因为哈希值的范围很大,冲突的概率极低,所以相对来说还是很难破解的。像 MD5,有 2^128 个不同的哈希值,这个数据已经是一个天文数字了,所以散列冲突的概率要小于 1/2^128。
如果我们拿到一个 MD5 哈希值,希望通过毫无规律的穷举的方法,找到跟这个 MD5 值相同的另一个数据,那耗费的时间应该是个天文数字。所以,即便哈希算法存在冲突,但是在有限的时间和资源下,哈希算法还是被很难破解的。
如此防止数据库中的信息被「脱裤」?
前面提到,将密码使用 md5 加密存储就可以了吗? 事实上这仍不安全,比如黑客已经知道了密码 12345 的 md5 值是 827ccb0eea8a706c4c34a16891f84e7b ,再去查已经获取的密码数据,如果值也是 827ccb0eea8a706c4c34a16891f84e7b ,那么就可以推断出密码是 12345,假如黑客将常用的密码及其哈希值维护一成一个字典(彩虹表),与获取的密码数据,很快就可以识别出哪些密码是常用的密码,仍然可以获取这部分用户的密码信息。
解决方法你可能已经想到了,就是对简单的密码进行加盐(salt)。加盐,也可理解为为密码加点佐料后再进行 hash 运算。比如原密码是 12345 ,加盐后可能是12ng34qq5zz,再对加盐后的密码进行 hash 运算得到的哈希值就与原密码的哈希值完全不同了。而且加盐的方式有很多种,可以是在头部加,可以在尾部加,还可在内容中间加,这样即使用户使用的是最常用的密码,黑客拿到密文后破解的难度也很高。安全和攻击是一种博弈关系,不存在绝对的安全。所有的安全措施,只是增加攻击的成本而已。
哈希算法有哪些应用?
1、安全加密
最常用于加密的哈希算法是 MD5 (MD5 Message-Digest Algorithm,MD5 消息摘要算法)和 SHA (Secure Hash Algorithm,安全散列算法),DES(Data Encryption Standard,数据加密标准), AES(Advanced Encryption Standard,高级加密标准),感兴趣的可以网上搜索一下。
2、唯一性校验
现在各种网盘都有这样的功能,假如你上传的文件已经在他们的服务器上,就可以实现秒传,如何判断你要传的文件是已经存在了呢,就是通过哈希算法。服务器上已经存储的文件按文件内容的二进制串生成啥希值,获取用户上传的文件内容的哈希值,对比已有的哈希值,如果找到说明已经存在,真接链接到已存在的文件即可实现秒传。至于非常大的文件,可以只获取文件内容的前 n 位,中间 m 位,最后 k 位来对比,从而加快速度。
类似的,在海量图库中搜索给定图片,查找重复大文件,信息摘要,数字证书都使用了哈希算法。
3、数据校验。
我们都用过电驴、迅雷、快播等下载过电影,下载的原理是基于 P2P 协议的。我们从多个机器上并行下载一个 2GB 的电影,这个电影文件可能会被分割成很多文件块(比如可以分成 100 块,每块大约 20MB)。等所有的文件块都下载完成之后,再组装成一个完整的电影文件就行了。
我们知道,网络传输是不安全的,下载的文件块有可能是被宿主机器恶意修改过的,又或者下载过程中出现了错误,所以下载的文件块可能不是完整的。如果我们没有能力检测这种恶意修改或者文件下载出错,就会导致最终合并后的电影无法观看,甚至导致电脑中毒。现在的问题是,如何来校验文件块的安全、正确、完整呢?
通过哈希算法,对 100 个文件块分别取哈希值,并且保存在种子文件中。哈希算法有一个特点,对数据很敏感。只要文件块的内容有一丁点儿的改变,最后计算出的哈希值就会完全不同。所以,当文件块下载完成之后,我们可以通过相同的哈希算法,对下载好的文件块逐一求哈希值,然后跟种子文件中保存的哈希值比对。如果不同,说明这个文件块不完整或者被篡改了,需要再重新从其他宿主机器上下载这个文件块。这样就实现了数据完整性的校验。
4、区块链
区块链是一块块区块组成的,每个区块分为两部分:区块头和区块体。区块头保存着自己区块体和上一个区块头的哈希值。因为这种链式关系和哈希值的唯一性,只要区块链上任意一个区块被修改过,后面所有区块保存的哈希值就不对了。区块链使用的是 SHA256 哈希算法,计算哈希值非常耗时,如果要篡改一个区块,就必须重新计算该区块后面所有的区块的哈希值,短时间内几乎不可能做到。
5、分布式之负载均衡
我们知道,负载均衡算法有很多,比如轮询、随机、加权轮询等。那如何才能实现一个会话粘滞(session sticky)的负载均衡算法呢?也就是说,我们需要在同一个客户端上,在一次会话中的所有请求都路由到同一个服务器上。
5、分布式之数据分片
假如有个 1T 大小的日志文件,里面记录了用户搜索的关键词,要统计每个关键词被搜索的次数,如何做呢?
文件很大,没法放在一台机器的内存中,就对数据进行分片,在多台机器进行处理,假如有 n 台机器,就将 1T 大小的日志文件分成 n 份,我们从 1T 的日志文件中,依次读出每个搜索关键词,并且通过哈希函数计算哈希值,然后再跟 n 取模,最终得到的值,就是应该被分配到的机器编号,记为 K,将该关键词输出到文件 K 中。这样相同的关键词就会分配到同一台机器,在这 n 台机器分别求出各自被分配到的关键词被搜索的次数,最后再合并一起。
实际上,这里的处理过程也是 MapReduce 的基本设计思想。
6、分布式存储
现在互联网面对的都是海量的数据、海量的用户。我们为了提高数据的读取、写入能力,一般都采用分布式的方式来存储数据,比如分布式缓存。我们有海量的数据需要缓存,所以一个缓存机器肯定是不够的。于是,我们就需要将数据分布在多台机器上。
我们可以借助前面描述的数据分片思想,对数据取哈希值,对机器个数取模,得到的值就是缓存所在的机器编号。
但是,如果数据增多,原来的 10 个机器已经无法承受了,我们就需要扩容了,比如扩到 11 个机器,这时候麻烦就来了。原来对 10 取模,现在对 11 取模,会导致取模的结果有变化,原来映射到机器 A,现在映射到机器 B,在机器 B 上是找不到缓存数据的,这样就相当于,缓存中的数据一下子就都失效了。所有的数据请求都会穿透缓存,直接去请求数据库。这样就可能发生雪崩效应,压垮数据库。
所以,我们需要一种方法,使得在新加入一个机器后,并不需要做大量的数据搬移。这时候,一致性哈希算法就要登场了。
详见 一致性哈希算法-维基百科「https://en.wikipedia.org/wiki/Consistent_hashin」。
假设我们有 k 个机器,数据的哈希值的范围是 [0, max]。我们将 hash 值对 m 取横,划分成 m 个小区间(m 远大于 k),每个机器负责 m/k 个小区间。当有新机器加入的时候,我们就将某几个小区间的数据,从原来的机器中搬移到新的机器中。这样,既不用全部重新哈希、搬移数据,也保持了各个机器上数据数量的均衡。
Python 中的哈希
1、hash() 函数
函数返回对象的哈希值。返回的哈希值是使用一个整数表示,通常使用在字典里,以便实现快速查询键值。参数 object 输入是数字类型时,是根据数值来计算的,比如 1 和 1.0 计算出来是一样的哈希值,因此说这个函数是不区分不同的数值类型。
>>> help(hash)
Help on built-in function hash in module builtins:
hash(obj, /)
Return the hash value for the given object.
Two objects that compare equal must also have the same hash value, but the
reverse is not necessarily true.
2、hashlib 库
Python 标准库的 hashlib 提供了常见的摘要算法,如 MD5,SHA1 等。使用方法请访问链接:「https://kuanghy.github.io/2015/06/16/python-hashlib」。
Help on module hashlib:
NAME
hashlib - hashlib module - A common interface to many hash functions.
DESCRIPTION
new(name, data=b'', **kwargs) - returns a new hash object implementing the
given hash function; initializing the hash
using the given binary data.
Named constructor functions are also available, these are faster
than using new(name):
md5(), sha1(), sha224(), sha256(), sha384(), sha512(), blake2b(), blake2s(),
sha3_224, sha3_256, sha3_384, sha3_512, shake_128, and shake_256.
More algorithms may be available on your platform but the above are guaranteed
to exist. See the algorithms_guaranteed and algorithms_available attributes
to find out what algorithm names can be passed to new().
NOTE: If you want the adler32 or crc32 hash functions they are available in
the zlib module.
Choose your hash function wisely. Some have known collision weaknesses.
sha384 and sha512 will be slow on 32 bit platforms.
写在最后
哈希算法还有很多应用,如 db2 分区数据库中如何将数据均衡地存储在各个分区中,网络协议中的 CRC 校验,git commit id 等。
别外说下弱密码,比如农村养老金,补助发放,保险缴款等银行卡,初始密码就是 111111 或 123123 或 123456,你不改其实也没有明确限制你不可用,所以还是客观存在的,不要奢望酒店,饭店,便利店,超市能有安全的 IT 服务,在这些平台尽量不要使用自己常用的密码。如果觉得密码已经泄露,请及时修改密码,建议每个网站都使用不同的密码,采取固定+浮动的方式设计你的密码,当然,你也可以借助哈希算法设计你的密码。
(完)
推荐阅读:
-含福利
以上是关于哈希算法-如何防止隐私信息被「脱裤」的主要内容,如果未能解决你的问题,请参考以下文章
21 | 哈希算法(上):如何防止数据库中的用户信息被脱库?