基于彩虹表碰撞法破解SHA/MD5等hash加密——半非暴力破解哈希逆运算

网上查了一下彩虹表攻击相关资料,发现大部分文章说得都不太清楚,于是扒了Philippe Oechslin最初的paper《Making a Faster Cryptanalytic Time-Memory Trade-Of》,再结合开源项目RainbowCrack-NG(https://github.com/inAudible-NG/RainbowCrack-NG/tree/master),把整个彩虹表攻击原理及其实现方法做了梳理,方便大家理解。

背景知识

哈希加密算法应用范围广泛,包括但不限于:Unix风格的用户密码(Linux、*BSD、Solaris、AIX、QNX等)、macOS、Windows、Web应用程序(如WordPress)、办公软件(如Notes、Domino)、数据库(如MySQL)、文件系统和磁盘(macOS.dmg、Windows BitLocker)、压缩软件(ZIP、RAR、7Z)、文档文件(PDF、Microsoft Office)等。

常见的哈希加密算法有MD5、SHA1、SHA256、SHA3等,他们都有一个特点,就是算法不可逆,要想通过密文倒推加密前的明文,最可靠的办法是暴力穷举,但其耗时往往是无法接受的。

以14位字母和数字的组合密码为例,共有(26*2+10)^{14}\approx 1.2*10^{25}种可能,即使电脑每纳秒计算一个hash值,也需要4亿年才能穷举,其耗时是不可接受的。

另一种办法是查表法,提前将穷举的结果的原文/密文对保存下来,拿到密文后通过查表找到对应的原文,也就是用空间换时间,但是这种空间代价也是无法接受的,还是以14位字母和数字的组合密码为例,假设密文长度为32字节,那么需要存储

(32+14)*2*10^{25}=9.2*10^{26}\approx 8.4*10^{14}TB

假设1TB硬盘价格1RMB,硬盘费用在8百万亿元人民币,是无法承受的。

为此,有人提出了一种碰撞算法来实现暴力破解哈希加密,并命名为彩虹表密码攻击法,该方法是查表法和暴力穷举法的折中优化,在可接受的存储容量基础上,大大减少计算量。

彩虹攻击技术原理

彩虹攻击本质上是一种暴力破解方法,同时采用了空间换时间的思路,即预先针对某种哈希加密算法生成彩虹表,然后使用彩虹表里面相对少量的值表征了整个超大密码空间值。要弄懂彩虹攻击原理,我们先要弄懂Hash函数工作原理,然后设计一个将哈希值散列到明文空间的Reduction函数,然后再理解彩虹链和彩虹表,最后理解碰撞检测原理。不着急,一个一个来

Hash函数

以最常用的MD5函数为例,我们借助node.js里面的md5模块

var crypto = require('crypto');
var md5 = crypto.createHash('md5');

var result = md5.update('MyPasswad').digest('hex');

// 输出:5342972619fca82e494ff2fb688f5da2
console.log(result);
// 输出:32
console.log(result.length)

其功能是将输入的 MyPasswad 字符串通过哈希计算,散列对应到一个长度为128bit的大数空间中的某个数,最后通过16进制打印出来。其中有几个重点:

1.均匀性:尽量确保不同的字符串经过哈希计算后会对应到不同的大数,很难找到两个不同的字符串具有相同的哈希值,即实现哈希散列的均匀化,具备高抗碰撞性;

2.单向性:无法从哈希加密结果反向计算加密前的明文字符串,即该计算是单向的,不可逆;

3.唯一性:同一个字符串经过某种哈希计算后得到唯一的大数值。

Reduction函数

Reduction函数的功能是将Hash计算结果的大数转换为一个字符串,即实现大数空间到密码明文空间的映射。

同时Reduction函数必须实现大数空间到字符串空间的均匀映射,与Hash函数将字符串映射到大数类似,实现大数到字符串的反向均匀映射,这个均匀性非常重要,是彩虹攻击有效性的关键。

其实要做到这一点也简单,可以利用哈希函数均匀性,简单取模即可。解释一下,具体做法是将所有明文看成一个集合P,P集合包含的元素个数为N,哈希计算结果的大数集合记为C,Reduction函数记为R,即P^{idx}_{i}=R(C_{i})=C_{i}\%N,其中P^{idx}_{i}P_{i}在P集合中的索引值。因为哈希函数具备均匀性,那么用其取模即可实现均匀散列到从0到N-1的集合索引空间,也就相当于均匀散列到P集合空间。

当然,Reduction函数在彩虹攻击中实际实现会略有不同,具体后面会说到。

彩虹链

介绍完前面的Hash函数和Reduction函数后,我们就可以来说说彩虹链了,因为彩虹链的建立就是基于这两个函数。

先说彩虹链机制:记Hash函数为H,Reduction函数为R,对一个明文P_{0}做Hash计算,得到一个密文C_{0},即C_{0}=H(P_{0}),对C_{0}做一次Reduction计算得到一个新的明文P_{1},即P_{1}=R(C_{0}),在对P_{1}进行一次Hash计算,以此往复进行t次计算,最终得到C_{t},如下

P_{0}\overset{H}{\rightarrow}C_{0}\overset{R}{\rightarrow}P_{1}\overset{H}{\rightarrow}C_{1}\overset{R}{\rightarrow}P_{2}\overset{H}{\rightarrow}C_{2}\overset{R}{\rightarrow}... ...P_{t}\overset{H}{\rightarrow}C_{t}

如此,便形成了一个长度为t的链条,称为彩虹链。彩虹链具有可复现性,即P_{0}经过t次H和R函数计算后能重复得到从C_{0}C_{t},以及P_{1}P_{t}等一系列值。基于复现性,我们可以用P_{0}C_{t}表征一整条彩虹链上的t组值,达到无损数据压缩效果。如果我们使用n条彩虹链使其n*tP_{i}值能覆盖全部的P集合空间,即覆盖所有明文的可能性,便可以达到和穷举一样的效果。但其存储空间只需要n组P_{0}C_{t}值,将近\frac{1}{t}的压缩率。当t值较大时,比如t=10000,能够大大节省存储空间。

彩虹表

基于彩虹链上述可复现特性,建立一张数据表,取彩虹链首位值存入该表,称之为彩虹表。因此彩虹表只有两列,第一列存的是彩虹链首值,第二列存的是彩虹链的尾值,如果存储的彩虹链条数为n,彩虹表便有n行数据。

基于Hash函数和Reduction函数的均匀性,一条哈希链能覆盖tP_{i}值,n条哈希链便能覆盖n*t个P集合空间值,假如n*t>N便能覆盖整个P集合。当然这是理想情况,实际情况下会有很多重复值出现,按照paper的的表述覆盖率p和n的取值之间满足如下关系:

p\geq \frac{1}{N} \sum_{i=1}^{n}\sum_{j=0}^{t-1}(1-\frac{it}{N})^{j+1}

碰撞检测(密码破译)

假如我们拿到一个密文X,经过检索彩虹表第二列,如果命中了第k行数据,即C_{t}^{k},那么说明用于计算C_{t}^{k}P_{t}^{k}即为X的明文;

假如彩虹表中没能检索到X密文值,那么,我们可以对X执行以下操作X\overset{R}{\rightarrow}Y_{1}\overset{H}{\rightarrow}X_{1},然后用X1检索彩虹表第二列,如果命中了第j行数据,即C_{t}^{j},那么说明P_{t-1}^{j}X的明文,因为P_{t-1}^{j}\overset{H}{\rightarrow}C_{t-1}^{j}\overset{R}{\rightarrow}P_{t}^{j}\overset{H}{\rightarrow}C_{t}^{j},相当于X等于C_{t-1}^{j},而X的明文即为P_{t-1}^{j}

类似地,X的明文还有可能等于P_{t-x}^{y}(其中0<x<=t,0<=y<n),即X执行x次RH变换后的值能够在彩虹表第二列中检索到,说明X的明文可能处于该条数据对应的彩虹链中的某个环节。

在基于彩虹链的可复现性,我们只需要使用该条数据的第一列值,即P_{0}值,执行t次RH计算,复现该条彩虹链中的所有C_{i}P_{i}值,找到与X值相同的C_{i}值,那么对应的P_{i}值即为X密文对应的明文。

需要注意的是,即便找到了C_{t}^{j},任无法确保该条彩虹链中必然包含X值,毕竟Hash函数和Reduction函数无法做大绝对的唯一性和均匀性,所以误检是存在的,而且n值越大,误检率越高。

这一基于尝试性的检测过程,被称为碰撞检测,也是彩虹攻击破译密码的基本原理。

彩虹攻击技术实现

至此,彩虹攻击基本原理阐述完毕。同时也面临几个问题:

1.彩虹表中n条彩虹链的n个P_{0}值该如何选取?

2.如果单条彩虹链中出现循环,势必降低了覆盖率,该如何处理?

3.如果两条,或多条彩虹链中段出现重复值,后续便完全相同,也会降低覆盖率,该如何处理?

4.哈希表中存储链首的明文P_{0}值和链尾的哈希值C_{t}值真的合适吗?有没有更优选择?

第一个问题相对简单,基于Hash函数的均匀性,以及t值往往比较大,通常在2000以上,初值显得不是特别重要,只需要确保n个P_{0}值不重复即可。最简单的做法就是从P集合中取前n个值即可,往往这也是比较可取的方案,因为P集合前n个值通常是简单的弱密码,无形中将弱密码覆盖到了。

彩虹表存储优化

先来看第4个问题,以MD5为加密算法的10字符密码为例,如果存储明文和MD5值,第一列明文字符串需要10Byte,数据类型为string,第二列MD5密文需要16Byte,数据类型为byte,合计单个哈希链需要的存储空间为26Byte。假如需要存储10万亿条哈希链,需要的存储空间约为260TB。

请注意Reduction函数,其计算结果是一个Index,其数据类型为Int64。假如我们哈希表第一列存储明文P_{0}对应的Index值,只需要8Byte,第二列存储密文C_{t}经过Duduction计算后的Index值,也只需要8Byte。如此,单个哈希链需要的存储空间缩小到16Byte,10万亿条哈希链所需存储空间约为160TB,相较之前的方案缩减了100TB存储空间,将近40%。

同时需要注意,由于选用的Index只有64bit,因此P集合大小不得超过2^{64},约1.8*10^{19}个。以大小写字母+数字为例,当密码长度为11时,其P集合大小为

(26\times 2+10)^{11}=52889619055703280000\approx 2 ^ {65.5196}> 2 ^ {64}

因此Index选用Int64时,最多能用于破解长度不超过10的大小写字母+数字的密码。如果需要破解更复杂的密码,如长度为14的全字符密码

(26*2+10+14+18+1)^{14}\approx 2^{92}

因此Index需要选用Int128数据类型。Int128可以最多容纳全字符长度19的密码空间。

Reduction函数优化

要解决上述第2、3个问题,重点在于Reduction函数的设计。假如长度为t的彩虹链使用t个不同的Reduction函数,即:

P_{0}\overset{H}{\rightarrow}C_{0}\overset{R_{1}}{\rightarrow}P_{1}\overset{H}{\rightarrow}C_{1}\overset{R_{2}}{\rightarrow}P_{2}\overset{H}{\rightarrow}C_{2}\overset{R_{3}}{\rightarrow}... ...\overset{R_{t}}{\rightarrow}P_{t}\overset{H}{\rightarrow}C_{t}

第2个问题便迎刃而解,因为经过不同的Reduction函数便得到不同的值,不会形成循环;对于第3个问题,即便彩虹链中出现了相同值,只要不在同一个位置,后续值也不会重复。

要实现彩虹链不同环节的R函数也很简单,开源项目RainbowCrack-NG中是这样实现的

P^{idx}_{i}=R_{i}(C_{i})=(C_{i}+i)\%N

即:在取模的基础上,加上彩虹链位置偏移量。

需要注意的是,优化后的Reduction函数为碰撞检测环节增加了复杂性。在使用统一R函数的情况下,只需要执行t次H/R函数,就可以得到t个可用于碰撞检测的C值。当改进用不同R函数时,做彩虹链第i个位置的检测时,需要执行t-i次H/R函数

X\overset{R_{i+1}}{\rightarrow}P^{x}_{i+1}\overset{H}{\rightarrow}C^{x}_{i+2}\overset{R_{i+2}}{\rightarrow}......\overset{R_{t}}{\rightarrow}P^{x}_{t}\overset{H}{\rightarrow}C^{x}_{t}

P^{j}_{0}\overset{H}{\rightarrow}C^{j}_{0}\overset{R_{1}}{\rightarrow}P^{j}_{1}\overset{H}{\rightarrow}C^{j}_{1}\overset{R_{2}}{\rightarrow}......P^{j}_{i}\overset{H}{\rightarrow}C^{j}_{i}\overset{R_{i+1}}{\rightarrow}P^{j}_{i+1}... ...\overset{R_{t}}{\rightarrow}P^{j}_{t}\overset{H}{\rightarrow}C^{j}_{t}

假如彩虹表中第j行的第二列C^{j}_{t}==C^{x}_{t},那么用P^{j}_{0}计算得到C^{j}_{i}值,然后判断C^{j}_{i}是否等于X,如果相等,那么P^{j}_{i}便是X的明文。

在做碰撞检测过程中,肯定是从最简单的假设开始,即假设彩虹表中包含了X值,这时候不需要对X做H/R计算,直接检索即可。然后假设X位于彩虹链末端倒数第二的位置,这时候只需要对X做一次H/R计算。最坏的情况,即X的明文为P_{0}时,需要对X执行t次H/R函数,才能得到可用于碰撞检测的C^{x}_{t}值。从概率来说,大约都需要计算到彩虹链的中间处才能碰撞成功,因此需要对X执行H/R计算的次数如下:

1+2+3+......+\frac{t}{2}=(1+\frac{t}{2})\frac{t}{4}=0.125t^{2}+0.25t

相较于之前固定R函数只需要执行\frac{t}{2}次H/R计算,改进后的R函数需要t^{2}次H/R计算,当t值较大时,会大大增加计算量。

结束语

整个彩虹攻击可分为两个环节,第一个环节是彩虹表构建,第二个环节是密码碰撞检测破译过程。

重点在于第一个环节,而在彩虹表构建环节需要注意的是t值和n值的选取,即彩虹链的长度t和彩虹表中彩虹链的条数n。这两个值的选取由以下因素决定:

密码字符集
密码长度
期待成功率

有一个在线工具可以用来计算t值和n值,工具地址:Advanced Rainbow Table Calculator - TobTu

 需要说明以下这个工具里面的Tables字段,用于多个彩虹表的情况下,前面我们讨论的都仅限于单一彩虹表,实际使用过程中,通常会分多个彩虹表,这一点在Philippe Oechslin的paper中也有提到,在分多张彩虹表的情况下,密码破译成功率计算公式为:

其中l为分表个数。

同时在Reduction函数设计时,每个分表都会有各自不同的Reduction函数。在RainbowCrack-NG开源项目中,Reduction函数是这么设计的

P^{idx}_{ji}=R_{ji}(C_{ji})=(C_{ji}+j+i)\%N

其中j为当前彩虹表在l个彩虹表中的序号,i为当前所处彩虹链计算步骤。

至此,彩虹攻击原理及其实现解释完毕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ctbinzi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值