在彩虹表之前,已经出现了对哈希函数的破解算法,被称为“预计算的哈希链集”(Precomputed hash chains)。当面对要破解的哈希函数H,首先要定义一个函数R,该函数的定义域和值域与哈希函数相反,通过该函数可以将哈希值映射为一个与原文相同格式的值。需要强调的是,由于哈希函数是不可逆的,所以对于密文进行R运算几乎不可能得到明文原文。
H,R重复次数记为k,k值应该适当,k太大容易出现重复子链,太小存储空间太大。我们只存储每条链的起点和终点,若干条哈希链构成哈希链集。
已知一个密文,假设是y:
(i)对密文进行R运算R(y),将R(y)在终点表中查询;
若查询失败,继续进行H、R运算,查询;最多进行K次R运算,要么查询成功,要么破解失败。
若查询成功,执行步骤(ii)
(ii)查明文并验证
由查询匹配到的终点对应的起点,重复进行H、R运算:起点→m→y=H(m)→R(y)
起点→m→y是查明文过程,y→R(y)是验证过程。查到明文就是m。
y=D2A82C9A,R(y)=vfkkd,查表失败;H(vfkkd)=0CAFC376,R(0CAFC376)=crepa,查表成功。
相应起点终点分别是zhihu、crepa 。
查明文并验证:
一条哈希链代表了k组明文密文对,但它只存储起点终点,因此相对于暴力表大大节省了空间。哈希链破解最耗时的操作就是查询终点,由于终点数(链数)远远小于密文数,因此相对于暴力表大大节省了时间。
R函数的选取要求:
(1)将值域限定在固定的范围之内。事实上,在计算和下载彩虹表时,需要使用不同的库的。按照不同的哈希函数、字符集、密码长度 等分为很多个不同的库。
(2)尽量保证R的值域均匀分布,以减少碰撞。
然而实际上很难找到能满足这些要求的完美的R函数。当计算中发生碰撞时,就会出现如下的子链重复的问题:
二、彩虹表
为了解决前面“子链重复”的问题,不再使用单一的R函数,而是使用不同的Ri函数(故称为彩虹表)。
当两条链发生碰撞的位置不在同一位置时,后续的R函数的不一致使得链条的后续部分也不同,从而最大程度地减小了链条中的重复子链。同时,如果在极端情况下,两个链条有1/k的概率在同一序列位置上发生碰撞,导致后续链条完全一致,这样的链条也会因为末节点相同而检测出来,可以丢弃其中一条而不浪费存储空间。
不同的彩虹表使用的R函数集不同。
彩虹表的使用比哈希链集稍微复杂一些。
已知一个密文,假设是y:
(i)假设密文在k-1位置,对密文进行Rk运算Rk(y),将Rk(y)在终点表中查询;若查询成功,执行步骤(ii),若查询失败,
假设密文在k-2位置,对密文进行Rk-1、H,Rk运算,将结果Rk(H(Rk-1(y)))在终点表中查询;若查询成功,执行步骤(ii),若查询失败,
假设密文在k-3位置,......
假设密文在0位置,对密文进行反复Ri,H运算,将结果在终点表中查询;若查询成功,执行步骤(ii),若查询失败,破解失败。
(ii)查明文并验证
由查询匹配到的终点对应的起点,重复进行H、R运算:起点→m→y=H(m)→Ri(y)
起点→m→y是查明文过程,y→Ri(y)是验证过程。查到明文就是m。
举例:
y=85E4969A
假设密文在2位置:R3(y)查询失败;假设密文在1位置:R3(H(R2(y)))查询失败;
假设密文在0位置:R3(H(R2(H(R1(y)))))=biqkz查询成功;
彩虹表减少了没必要的重复子链,相对于哈希链集节省了空间;假设链长位2k+1(密文k个,明文K+1个),在最坏的情况下即破解失败的情况下,哈希链集进行K次R运算,彩虹表进行1+2+3...+K=k(K+1)/2次R运算,所以彩虹表比哈希链集耗时更多。这就是时空的平衡。
最常用的方法,就是加盐(salt),实际上相当于改变了哈希函数H的形式。由于彩虹表在构造和破解的过程中,反复用到了H,H如果发生了改变,则已有的彩虹表数据就完全无法使用,必须针对特定的H重新生成彩虹表,这样就提高了破解的难度。
转自:知乎Smallay