如果两个用户使用了同样的密码,那么一定他们的密码hash也一定相同。我们可以通过让每一个hash随机化,同一个密码hash两次,得到的不同的hash来避免这种攻击。
具体的操作就是给密码加一个随即的前缀或者后缀,然后再进行hash。这个随即的后缀或者前缀成为“盐”。正如上面给出的例子一样,通过加盐,相同的密码每次hash都是完全不一样的字符串了。检查用户输入的密码是否正确的时候,我们也还需要这个盐,所以盐一般都是跟hash一起保存在数据库里,或者作为hash字符串的一部分。
盐不需要保密,只要盐是随机的话,查表,彩虹表都会失效。因为攻击者无法事先知道盐是什么,也就没有办法预先计算出查询表和彩虹表。如果每个用户都是使用了不同的盐,那么反向查表攻击也没法成功。
最常见的错误就是在多次哈希加密中使用相同的盐值或者太短的盐值
盐值重复
每次哈希加密都使用相同的盐值是很容易犯的一个错误,这个盐值要么被硬编码到程序里,要么只在第一次使用时随机获得。这样加盐的方式是做无用功,因为两个相同的密码依然会得到相同的哈希值。攻击者仍然可以使用反向查表法对每个值进行字典攻击,只需要把盐值应用到每个猜测的密码上再进行哈希即可。如果盐值被硬编码到某个流行的软件里,可以专门为这个软件制作查询表和彩虹表,那么破解它生成的哈希值就变得很简单了。
用户创建账户或每次修改密码时,都应该重新生成新的盐值进行加密。
短盐值
如果盐值太短,攻击者可以构造一个查询表包含所有可能的盐值。以只有3个ASCII字符的盐值为例,一共有95x95x95=857,375种可能。这看起来很多,但是如果对于每个盐值查询表只包含1MB最常见的密码,那么总共只需要837GB的储存空间。一个不到100美元的1000GB硬盘就能解决问题。
同样地,用户名也不应该被用作盐值。尽管在一个网站中用户名是唯一的,但是它们是可预测的,并且经常重复用于其他服务中。攻击者可以针对常见用户名构建查询表,然后对用户名盐值哈希发起进攻。
为了使攻击者无法构造包含所有可能盐值的查询表,盐值必须足够长。一个好的做法是使用和哈希函数输出的字符串等长的盐值,比如SHA256算法的输出是256bits(32 bytes),那么盐值也至少应该是32个随机字节。
所以:哈希加盐最重要的两点就是盐值随机和长度够长
# -------------------------------------------
# Python简单密码加密程序
# 随机生成4位salt,与原始密码组合,通过md5加密
# Author : Lrg
# -------------------------------------------
# encoding = utf-8
from random import Random
from hashlib import md5
# 获取由4位随机大小写字母、数字组成的salt值
def create_salt(length = 4):
salt = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
len_chars = len(chars) - 1
random = Random()
for i in xrange(length):
# 每次从chars中随机取一位
salt += chars[random.randint(0, len_chars)]
return salt
# 获取原始密码+salt的md5值
def create_md5(pwd,salt):
md5_obj = md5()
md5_obj.update(pwd + salt)
return md5_obj.hexdigest()
# 原始密码
pwd = '20141124'
# 随机生成4位salt
salt = create_salt()
# 加密后的密码
md5 = create_md5(pwd, salt)
print '[pwd]\n',pwd
print '[salt]\n', salt
print '[md5]\n', md5
==>xrange() 函数用法与 range 完全相同,所不同的是生成的不是一个数组,而是一个生成器。
xrange 语法:
xrange(stop)
xrange(start, stop[, step])
start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range(0, 5);
stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
hash碰撞
因为hash函数是将任意数量的数据映射成一个固定长度的字符串,所以一定存在不同的输入经过hash之后变成相同的字符串的情况。加密hash函数(Cryptographic hash function)在设计的时候希望使这种碰撞攻击实现起来成本难以置信的高。但时不时的就有密码学家发现快速实现hash碰撞的方法。最近的一个例子就是MD5,它的碰撞攻击已经实现了。
碰撞攻击是找到另外一个跟原密码不一样,但是具有相同hash的字符串。但是,即使在相对弱的hash算法,比如MD5,要实现碰撞攻击也需要大量的算力(computing power),所以在实际使用中偶然出现hash碰撞的情况几乎不太可能。一个使用加盐MD5的密码hash在实际使用中跟使用其他算法比如SHA256一样安全。不过如果可以的话,使用更安全的hash函数,比如SHA256, SHA512, RipeMD, WHIRLPOOL等是更好的选择。