彩虹表

维基百科,自由的百科全书
跳转至: 导航搜索

彩虹表是一个用于加密散列函数逆运算的预先计算好的,常用于破解加密过的密码散列。 查找表常常用于包含有限字符固定长度纯文本密码的加密。这是以空间换时间的典型实践,在每一次尝试都计算的暴力破解中使用更少的计算能力和更多的储存空间,但却比简单的每个输入一条散列的翻查表使用更少的储存空间和更多的计算性能。使用加盐KDF函数可以使这种攻击难以实现。

彩虹表是馬丁·赫爾曼早期提出的简单算法[1]的应用。

Simplified rainbow table with 3 reduction functions

背景[编辑]

每一个需要密码认证的计算机都包含一个储存密码的数据库,摘要亦或是纯文本,有多种密码储存方式密码储存存在。因为这些表很容易被窃取,直接储存纯文本密码就会变得很危险。因此大多数数据库都储存一个用户密码的加密摘要。在这种系统中,没有人能夠—即便是认证系统本身都无法—简单地通过查表来确定用户密码。然而,当一个用户输入密码以供验证时,就会生成一个加密过的消息摘要和原来储存的密码(加密摘要)相比较,如果相同,那么就允许访问。

黑客在盗取到散列后的密码表后,并不能仅通过输入用户的散列后的密码(加密摘要)来获取权限(使用加密摘要作为输入密码并不可行,因为认证系统会把加密摘要再次进行散列,产生一个与储存的密码(加密摘要)不匹配的消息摘要)。为了获取用户的密码,黑客需要找到一个能产生相同加密摘要的密码。

彩虹表就是尝试仅通过加密摘要而获取用户密码的一个工具。

由于有更为简单的方法来逆向散列运算(hash reversal), 彩虹表并不总会被用到。暴力破解法字典攻击法英语dictionary attack是最简单的破解方法。但是,这些方法在面对储存有大量密码的系统时,乏力于储存用于逆向查找的所有选项和搜索大型的数据库。

为了解决大型密码库带来的困难,需要引入储存相对较小,但可以逆向形成长链密码的散列值的逆向查找表。虽然这样会花费更多的计算时间来破解单个的密码,但是这会使整体的字典小得多,因而可以储存密码更长的散列值。彩虹表正是基于此类连接技术改进,并提供一种称为碰撞链的解决方案的密码破解工具。

预先计算的散列链[编辑]

注:这里表达的散列链与哈希链中解释的是不同的散列连。

假设我们有一个哈希方程H和一个有限的密码集合P。我们需要预先计算出一个数据结构来帮我们决定哈希方程H的任意一个输出结果h是否可以通过密码集合P里面的一个元素p经哈希函数H(p) = h得到。实现这一目的的最简单的方法是计算出P集合内所有密码p的哈希值H(p)。但是这个方法要求Θ(|P|n),(n代表哈希函数H的一个输出值的大小,对于较大的|P|,n会变得过高)字节的空间来储存结果。

哈希链可以用来减少对于储存空间的需求。大致想法是通过定义一个归约函数(reduction function)R来影射散列值h在集合P中对应的密码p。(注意,这里的归约函数并不是真正意义上哈希函数的反函数。)然后通过用归约函数来替代哈希函数,形成交替的密码和哈希值。例如,如果P是6个字符的密码集合,而哈希值有32位长,那么他们形成的长链如下:


一个归约函数的例子:

已知一个32位的哈希值 -〉 获得哈希值的最后4个字符。

对于归约函数的唯一要求是,它需要能返回特定字符的纯文本值。

为了生成查找表,我们从初始密码集合P中随机选择一个子集,对子集中的每个元素计算长度为k的哈希链,然后保存每条哈希链的初始和末尾密码。我们把初始密码称为起点,把末尾密码称为终点。在上述哈希链的例子中,"aaaaaa"和"kiebgt"即为起点和终点,而哈希链中的其他密码或者哈希值均不会被保存。[來源請求]

现在,对于给定的哈希值h我们来计算它的逆(即找到对应的密码),从哈希值h开始,我们通过依次施加函数R和H生成一个哈希链。如果哈希链的任何节点与查找表的某个终点发生了碰撞,我们就可以找到与之对应的起点,然后用它重建对应的哈希链。而这个哈希链很可能包含了h,如果这样的话,那么它的前驱节点即为我们要寻找的密码p[來源請求]

例如,如果给定的哈希值为920ECF10,我们首先施加函数R:

因为"kiebgt"就是我们查找表的一个终点,所以我们找到对应的起始密码"aaaaaa",然后搜索由它生成的哈希链直到找到920ECF10:

因此,目标密码即为"sgfnyd"。

注意,这里的哈希链并不总是会包含我们要寻找的哈希值h;很可能以h开始的链会和起点h链之后的某个查找链重合。例如,我们可以从FB107E70的哈希链中找到kiebgt:

但FB107E70并不在以"aaaaaa"开头的链中。这种情况被称为误报。在这个例子中,我们可以忽略这个匹配然后继续扩增h链同时寻找下一个匹配。如果h的哈希链在扩增到长度为k后仍然没有发生匹配,那么与之对应的密码一定不在查找表的任何哈希链中。


查找表的内容并不依赖于查询的哈希值。它是在一次性建立之后,被无修改的重复用于查找。增加链的长度可以减小表的规模,但同时也会增加查询时间,这就是彩虹表空间换时间的折中策略。在单元链的最简单情况下,查询速度会非常快,但表也会非常大。一旦链变得更长,查找速度也会降下来,但表的规模变得更小了。

简单的哈希链方法有很多缺陷。最严重的问题是,如果两条链中的任何两个点碰撞(有同样的值)了,他们后续的所有点都将重合,这将导致在付出了同样的计算代价之后并不能使表尽可能多的覆盖到密码。由于链的前部并没有整个的保存,这使得碰撞不可能有效检测到。例如,如果链3的第三个值和链7的第二个值重合了,那么这两条链将覆盖几乎同样的值,但他们的终点值却不相同。哈希函数H本身不大可能产生碰撞,因为这就是它最重要的安全特性之一。但对于衰减函数R来说,由于他需要正确的覆盖掉可能的明文(即之前讨论的密码),所以不会是抗碰撞的。

其他的困难是由选择正确R函数的重要性导致的。选择恒等映射作为函数R要比暴力搜索好一点。只有当攻击者明确知道明文的可能取值是什么时,他/她才需要选择一个好的R函数使得时间和空间花费在这些可能的明文上,而不是整个可能的密文空间。尽管把R的取值限定到可能的明文空间能带来好处,但它的弊端是攻击者选择的用于生成哈希链的明文子集可能并不能覆盖所有的可能明文空间。设计一个能匹配明文期望分布的R函数也非常困难。

彩虹表[编辑]

为了解决简单的哈希链中的碰撞问题,彩虹表选用一系列相关的衰减函数R1,…,Rk来代替原先的衰减函数R。这样,如果两个哈希链发生碰撞并且重合,那么它们的碰撞必定发生在相同的位置,从而它们的终点也将相同。这样,我们可以通过后处理来对哈希链进行排序,从而找出并移除所有终点相同,因而可能是重复的链,并生成新的链来将整个表补充完整。这样得到的表中的链可能有碰撞的部分,但它们不会发生链的重合,从而大幅降低了碰撞的次数。[來源請求]

采用衰减函数列代替衰减函数将改变查找的方式。因为给定的哈希值可能出现在哈希链中的任意位置,我们需要计算k条不同的链:首先假定给定的哈希值出现在哈希链的最后一位(此时我们只需施加函数Rk),然后假定哈希值出现在哈希链的倒数第二位(此时我们依次施加函数Rk-1,H和Rk),依此类推,直至我们找到所需的密码。注意,如果我们错误地假定了目标哈希值在哈希链中的位置,可能会得到一条与表中的链部分重合的链,从而产生误报。


参考资料[编辑]

参见[编辑]

外部链接[编辑]