本文主要介紹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以上,祝君好運。