本文主要介绍MapReduce编程模型的原理和基于Hadoop的MD5暴力破解思路。
一、MapReduce的基本原理
Hadoop作为一个分布式架构的实现方案,它的核心思想包括以下几个方面:HDFS文件系统,MapReduce的编程模型以及RPC框架。无论是怎样的架构,一个系统的关键无非是存储结构和业务逻辑。HDFS分布式文件系统是整个Hadoop的基础。在HDFS文件系统之中,大文件被分割成很多的数据块,每一块都有可能分布在集群的不同节点中。也就是说在HDFS文件系统中,文件的情况是这样的:
文件保存在不同的节点上,而Hadoop是用于海量数据处理的,那么如何把分布在各个节点的数据进行高效的并发处理呢?Hadoop对此提供了不同的解决方案,比如yarn框架等。框架已经帮我们写好了很多的诸如任务分配,节点通信之类的事情。而我们要做的就是写好自己的业务逻辑,那么我们就要遵守Hadoop的编程规范,而这个编程规范就是MapReduce。
那么MapReduce的运行过程是怎么样的呢?且看下图:
1.从HDFS文件系统中读取文件,每一个数据块对应一个MapTask。
2.进行Map任务,逐行读取文件,每一行调用一次Map函数,数据被封装为一个键值对也就是图中的<k2,v2>。
3.将Map后的键值对进行归约,key值相同的value会被封装到一起。就行了图中的<k,{v1,v2,v3}>
4.归约后的键值对会被送到不同的Reduce中,执行Reduce任务,输出<k3,v3>到输出文件中。
弄懂了MapReduce的执行过程之后,我们就可以编写自己的逻辑来进行处理了。
二、MD5暴力破解的基本思路
还是先上图:
1.编程生成所有的密码明文文件。
2.将明文上传至HDFS文件系统中,在Map函数中实现MD5的求值。然后直接存入文件系统中中。
代码实现:
package com.test; import java.security.MessageDigest; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; /** * 目地很简单。不需要reduce处理,直接在Map中解决问题 * @author hadoop * */ public class Test { //定义Map处理类 static class TestMapper extends Mapper<LongWritable, Text, Text, Text>{ //重写map方法 public void map(LongWritable key, Text value, Context context)throws InterruptedException { try{ //生成MD5 String keyStr=value.toString(); String MD5=getMD5(keyStr); context.write(new Text(keyStr), new Text(MD5)); }catch (Exception e){ e.printStackTrace(); } } } /** * MD5计算 * @param str * @return */ public static String getMD5(String str) { try { // 生成一个MD5加密计算摘要 MessageDigest md = MessageDigest.getInstance("MD5"); // 计算md5函数 md.update(str.getBytes()); // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符 // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值 byte[] encrypt = md.digest(); StringBuilder sb = new StringBuilder(); for (byte t : encrypt) { String s = Integer.toHexString(t & 0xFF); if (s.length() == 1) { s = "0" + s; } sb.append(s); } String res = sb.toString(); return res; } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) throws Exception { //必须要传递的是自定的mapper和reducer的类,输入输出的路径必须指定,输出的类型<k3,v3>必须指定 //将自定义的MyMapper和MyReducer组装在一起 Configuration conf=new Configuration(); String jobName=Test.class.getSimpleName(); //首先写job,知道需要conf和jobname在去创建即可 Job job = Job.getInstance(conf, jobName); //如果要打包运行改程序,则需要调用如下行 job.setJarByClass(Test.class); //读取HDFS內容:设置输入路径 FileInputFormat.setInputPaths(job, new Path(args[0])); //指定解析<k1,v1>的类(谁来解析键值对) //*指定解析的类可以省略不写,因为设置解析类默认的就是TextInputFormat.class job.setInputFormatClass(TextInputFormat.class); //指定自定义mapper类 job.setMapperClass(TestMapper.class); //指定map输出的key2的类型和value2的类型 <k2,v2> //下面两步可以省略,当<k3,v3>和<k2,v2>类型一致的时候,<k2,v2>类型可以不指定 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(Text.class); //分区(默认1个),排序,分组,规约 采用 默认 // job.setCombinerClass(null); //接下来采用reduce步骤 //指定自定义的reduce类 // job.setReducerClass(null); //指定输出的<k3,v3>类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); //指定输出<K3,V3>的类 //下面这一步可以省 // job.setOutputFormatClass(TextOutputFormat.class); //指定输出路径 FileOutputFormat.setOutputPath(job, new Path(args[1])); //写的mapreduce程序要交给resource manager运行 job.waitForCompletion(true); } }
这里为什么不用Reduce过程?
Reduce是对归约后的键值对进行处理的,但是可以看见,我们的明文都是唯一的,经过Map后输出的键值对的Key都是不一样的,归约之后仍然如此,所以没有必要在Reduce过程中进行其他操作。
另外我之前的想法是不在map中处理,而是将Map中读取到的文件内容直接输出到Reduce,然后在Reduce中进行MD5的计算,但是从Map中传输过来的数据总会多出一些行,导致计算出错。(这个我也没能弄懂怎么回事,有大佬知道的可以靠诉我)
三、数据查询
有了上一步生成的数据,我们就可以做数据的查询了。生成的文件仍然是在HDFS文件系统中,通过终端输入参数(可以是明文或者是密文),然后用MapReduce进行查找,结果输出到文件中。
代码:
package com.test; import java.security.MessageDigest; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; /** * 目地很简单。不需要reduce处理,直接在Map中解决问题 * @author hadoop * */ public class Test { private static String s=null; //定义Map处理类 static class TestMapper extends Mapper<LongWritable, Text, Text, Text>{ //重写map方法 public void map(LongWritable key, Text value, Context context)throws InterruptedException { try{ //查询MD5的值 int index=value.find(s); if(index>=0){ System.out.println("=================="+value.toString()); context.write(new Text("result"), value); } }catch (Exception e){ e.printStackTrace(); } } } /** * MD5计算 * @param str * @return */ public static String getMD5(String str) { try { // 生成一个MD5加密计算摘要 MessageDigest md = MessageDigest.getInstance("MD5"); // 计算md5函数 md.update(str.getBytes()); // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符 // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值 byte[] encrypt = md.digest(); StringBuilder sb = new StringBuilder(); for (byte t : encrypt) { String s = Integer.toHexString(t & 0xFF); if (s.length() == 1) { s = "0" + s; } sb.append(s); } String res = sb.toString(); return res; } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) throws Exception { //必须要传递的是自定的mapper和reducer的类,输入输出的路径必须指定,输出的类型<k3,v3>必须指定 //将自定义的MyMapper和MyReducer组装在一起 //参数(明文或者MD5值) s=args[2]; Configuration conf=new Configuration(); String jobName=Test.class.getSimpleName(); //首先写job,知道需要conf和jobname在去创建即可 Job job = Job.getInstance(conf, jobName); //如果要打包运行改程序,则需要调用如下行 job.setJarByClass(Test.class); //读取HDFS內容:设置输入路径 FileInputFormat.setInputPaths(job, new Path(args[0])); //指定解析<k1,v1>的类(谁来解析键值对) //*指定解析的类可以省略不写,因为设置解析类默认的就是TextInputFormat.class job.setInputFormatClass(TextInputFormat.class); //指定自定义mapper类 job.setMapperClass(TestMapper.class); //指定map输出的key2的类型和value2的类型 <k2,v2> //下面两步可以省略,当<k3,v3>和<k2,v2>类型一致的时候,<k2,v2>类型可以不指定 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(Text.class); //分区(默认1个),排序,分组,规约 采用 默认 // job.setCombinerClass(null); //接下来采用reduce步骤 //指定自定义的reduce类 // job.setReducerClass(null); //指定输出的<k3,v3>类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); //指定输出<K3,V3>的类 //下面这一步可以省 // job.setOutputFormatClass(TextOutputFormat.class); //指定输出路径 FileOutputFormat.setOutputPath(job, new Path(args[1])); //写的mapreduce程序要交给resource manager运行 job.waitForCompletion(true); } }
四、导出JAR包放到Hadoop中运行
把文件导出成JAR包,在终端使用命令
生成密文:
bin/hadoop jar [jar包路径] [输入文件路径] [输出路径]
查询
bin/hadoop jar [jar包路径] [输入文件路径] [输出路径] [密文或者明文]
生成的密文结果实例:
查询的结果示例:
ok以上,祝君好运。