散列(又叫Hash)和加密在身份校验、敏感信息传输等应用中用途广泛。
把他们放在一起写的原因是,网上太多资料,将散列和加密的概念混为一谈,误导性极大。
由于编码/解码运算和上述二者有一定的相似之处,因此放在这里一并讲述。
注:本文中,为了表述严谨,所有的“位”代表的是字符个数之意,而bit则指代计算机数据中的基本单位比特。
1.三者的共同点
它们都表述了一种映射关系,将原始数据A映射到新的数据B。
在散列运算中,B一般称之为摘要,英文为digest. 其代表算法有MD5,SHA1, SHA256等。从A -> B的运算,称之为散列、哈希;从B -> A的运算(严格意义上来说,它不算一种运算)是不存在的,当然可以通过一些其他的手段比如查表法来实现,这个在下文中会谈到。
在加密运算中,A一般称之为明文或原文,B一般称之为密文。A->B的运算,称为加密;反之,即为解密。
而在编码中,A->B的运算称为编码,反之为解码。
2.三者的区别
散列
在散列运算中,是将一个较长的数据A,运算成为一个较短的数据B,如MD5的128bits,SHA1的160bits。
以MD5为例,谈谈它的几个重要特点。
- 不可逆。即无法找到一个算法,从B逆推出A。
- A的微小改变,会引起B的显著不同。举个例子,动手计算下字符A的MD5值为0cc175b9c0f1b6a831c399e269772661,B的MD5值为92eb5ffee6ae2fec3ad71c777531578f,相差巨大。
- 必然存在多个数据有相同的MD5值。从一个接近无限的元素集合映射到一个有限的元素集合,必然有多个元素A被映射到相同的元素B。
基于以上三点,MD5的一个重要应用就是,证明数据在传输过程中有没有受损,或是被篡改。
如在网络上下载一些较大的文件如系统镜像时,下载站通常会提供MD5校验值,下载完成后对文件计算MD5,与网站提供的值比对,即可知道文件下载的是不是正确的,或是有没有损坏。
又如网站登录时,服务端明文保存用户名密码是一件极为不安全的事情,因此在过去很长的一段时间里,网站都是保存用户密码的MD5值。用户登录时,js会计算并提交密码的MD5值到服务端作比对,如果一致,则认为登录成功。
上述做法看上去没什么问题。由于MD5不能逆推出原文,因此,暴力穷举成为了唯一的办法。所谓的暴力穷举,指的是计算一定范围内字符串的MD5值,并建立反向映射关系。比如我计算出“123456”的MD5值是E10ADC3949BA59ABBE56E057F20F883E,我把它存在一张表里,key是E10ADC3949BA59ABBE56E057F20F883E,value是“123456”。再遇到同样的MD5值,就知道其原文是“123456”。因此,随着我计算范围的扩大,这个表会越来越大,覆盖的原文的面积也会越来越广。这样的MD5-原文的反向映射表,通常称为彩虹表。因此,为什么再三强调密码要够复杂,弱密码的MD5早已存在于彩虹表里,被查表破解是分分钟的事情。实际上,查表破解一直以来就是一个经典的破解办法,其实质,就是穷举。
查表,是从MD5“逆推出”原文的唯一办法。
针对暴力查表破解,人们又想出了一个新的解决方法,即加盐。所谓加盐,即在原始的密码后加上“盐”,并且,每个用户的盐不同。这个盐是保存在服务端的。登录时,先从服务端获取该用户的盐值,并加到用户的密码字符串后,然后再计算MD5值提交验证。由于每个用户的盐不一样,所以,即使用户用了相同的密码,其MD5都是不一样的,因为加盐后的原文不一样了。加盐可以说是现今比较安全的一种做法,毕竟Hacker不可能针对每个用户的不同的盐值去单独建立一张彩虹表,成本太高,收益太小。
最后谈谈MD5碰撞。所谓的MD5碰撞算法,指的是,在已知原文A和MD5值B的情况下,通过该碰撞算法,在有限的时间里,构造出一个A‘,拥有相同的MD5值B。
该算法的潜在应用,更多的在于伪造、篡改信息。比如我今天发出一封邮件,邮件内容是A:“往Jack账户上转帐10000元。”,并把该邮件的MD5值通过另外一种方式发给了收件人。那么通过该碰撞算法,我们在截获该邮件后,通过MD5碰撞,构造出邮件A‘:“往John账户上转帐10000元。”,假设二者的MD5相同,收件人收到邮件并比对MD5后,确认邮件无误,但是却将钱转给了别人。
看起来很危险,但实际操作起来难度要大的多。因为通常情况下,A和A‘是大相径庭的两个数据,相似度极低,可能A有10个字节,A’却有上百MB;即便能让它们在特定格式下,可见内容一样,但大小差异如此之大,收件人多半会起疑心。
因此,针对所谓MD5不安全、被破解的“大新闻”,还是不要像某些媒体一样,“见得风是得雨”。
未完待续。