“Hash算法也就是我们常说的散列算法,其发展至今,想简单的说清楚各种算法背后的原理细节还是比较难的,除非你是做加密相关的行当,需要自己编写(或破解)算法。否则,对于大多数使用者来说,我们通过一些简单的概念来对Hash的思想有一个了解就可以了。这也是本文的主旨。”
为什么要Hash?
因为有很多场景下,我们需要用一组比原文件的体积小的多的字符来标识源文件,比如文件下载的时候提供源文件的hash值。
还有一些场景,我们希望原文内容不被直接看到,就用其hash来标识原文内容,比如用户注册时填写的密码,就是用hash值来存储到数据库中。
Hash是如何做到的?
要实现Hash,必须满足两个要求:第一:将原来体积较大的文件,用若干个字符来表示;第二:保证源文件或文本内容中的每一个字节的变动,都会对最终结果产生影响。或许你会想到,求模貌似可以做到,没错,我们看看下面的代码:
\# 构造散列函数
def hash_method(org_text):
return org_text % 8
print(hash_method(233))
print(hash_method(234))
print(hash_method(235))
\# 输出结果
1
2
3
上面提到的2个要求,通过取模运算,确实实现了。然而,单纯的取模还是过于简单了,通过对比一组连续的结果,还是能猜出来用了取模算法,以及用的取模的常数是什么的。所以我们可以再加入一个异或预算,一个位移,或一个加法,减法等,这样就比较难推测出原文本是经过何种算法来计算,即使知道了算法,想要推测出原文本也只能使用穷举法,但这是要花费很大的代价的。
上面说的思路当然还是相对简单的,而像MD5、SHA1这些常用的Hash算法,其本质思路与我们说的类似,但是会加入更多的循环和计算,算法本身足够复杂,以加强散列函数的可靠性。
常用的Hash算法
MD5(RFC 1321)是 Rivest 于1991年对 MD4 的改进版本。它对输入仍以 512 位分组,其输出是 128 位。MD5 比 MD4 复杂,并且计算速度要慢一点,更安全一些。MD5 已被证明不具备"强抗碰撞性",一方面对于2组只有微小差别的字符串,MD5有可能输出相同的值,另一方面MD5算法在网络上已经有很多原文和Hash的词典库,直接使用MD5得到的Hash值,可以通过这些词典库找到原文本,所以如果要用MD5,建议给原文本做一些非常规的修改,比如附加一个随机值的方法,然后再做MD5,以降低被词典破解的可能,该策略又称为MD5加点盐(salt值)。
SHA (Secure Hash Algorithm)是一个 Hash 函数族,由 NIST(National Institute of Standards and Technology)于 1993 年发布第一个算法。SHA家族算法的种类很多,有SHA0、SHA1、SHA256、SHA384等等,它们的计算方式和计算速度都有差别。目前知名的 SHA-1 在 1995 年面世,它的输出为长度 160 位的 hash 值,因此抗穷举性更好。SHA-1 设计时基于和 MD4 相同原理,并且模仿了该算法。SHA-1比MD5更安全,不过在2017年,被google等机构的研究人员找到了一例SHA1碰撞,虽然其碰撞代价是十分大的,但是如果你的场景十分敏感,还是建议使用SHA256,现在这些算法都有现成的库可以用,使用起来还是很方便的。
总结
Hash算法对于相同的原文件或文本输出固定的值,所以总是存在类似彩虹表这样的东西,来辅助得到Hash值的明文。为此,无论使用哪种Hash算法,都建议加点盐。在笔者做的项目中,密码的存储是用Hmac算法系列中的HmacSHA256,其结合了MD和SHA两种Hash算法的特性。HMAC算法需要额外提供一个密钥,该密钥会参与一系列复杂的运算,有点类似MD5加盐的思想,但是由于结合了SHA256,所以更安全。