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