MD5的介紹
從本質上來說,MD5並不是一種加密算法,其全稱是Message Digest Algorithm MD5(中文名:信息摘要算法第五版),爲計算機安全領域廣泛使用的一種散列函數,主要用於確保信息傳輸完整一致。是計算機廣泛使用的雜湊算法之一(又譯摘要算法、哈希算法),主流編程語言普遍已有MD5實現。MD5將數據(如漢字)運算爲另一固定長度值,是雜湊算法的基礎原理,MD5的前身有MD2、MD3和MD4。MD5的作用是讓大容量信息在用數字簽名軟件簽署私人密鑰前被"壓縮"成一種保密的格式(就是把一個任意長度的字節串變換成一定長的十六進制數字串)。信息摘要算法除了MD5以外,其中比較有名的還有sha-1、RIPEMD以及Haval等。
MD5的應用
一致性驗證
MD5的典型應用是對一段信息(Message)產生信息摘要(Message-Digest),以防止被篡改。比如,在UNⅨ下有很多軟件在下載的時候都有一個文件名相同,文件擴展名爲.md5的文件,在這個文件中通常只有一行文本,大致結構如:
MD5 (tanajiya.tar.gz) = 0ca175b9c0f726a831d895e269332461
這就是tanajiya.tar.gz文件的數字簽名。MD5將整個文件當作一個大文本信息,通過其不可逆的字符串變換算法,產生了這個唯一的MD5信息摘要。爲了讓讀者朋友對MD5的應用有個直觀的認識,筆者以一個比方和一個實例來簡要描述一下其工作過程:
大家都知道,地球上任何人都有自己獨一無二的指紋,這常常成爲公安機關鑑別罪犯身份最值得信賴的方法;與之類似,MD5就可以爲任何文件(不管其大小、格式、數量)產生一個同樣獨一無二的“數字指紋”,如果任何人對文件做了任何改動,其MD5值也就是對應的“數字指紋”都會發生變化。
我們常常在某些軟件下載站點的某軟件信息中看到其MD5值,它的作用就在於我們可以在下載該軟件後,對下載回來的文件用專門的軟件(如Windows MD5 Check等)做一次MD5校驗,以確保我們獲得的文件與該站點提供的文件爲同一文件。利用MD5算法來進行文件校驗的方案被大量應用到軟件下載站、論壇數據庫、系統文件安全等方面。
數字證書
MD5的典型應用是對一段Message(字節串)產生fingerprint(指紋),以防止被“篡改”。舉個例子,你將一段話寫在一個叫 readme.txt文件中,並對這個readme.txt產生一個MD5的值並記錄在案,然後你可以傳播這個文件給別人,別人如果修改了文件中的任何內容,你對這個文件重新計算MD5時就會發現(兩個MD5值不相同)。如果再有一個第三方的認證機構,用MD5還可以防止文件作者的“抵賴”,這就是所謂的數字簽名應用。
安全訪問認證
MD5還廣泛用於操作系統的登陸認證上,如Unix、各類BSD系統登錄密碼、數字簽名等諸多方面。如在UNⅨ系統中用戶的密碼是以MD5(或其它類似的算法)經Hash運算後存儲在文件系統中。當用戶登錄的時候,系統把用戶輸入的密碼進行MD5 Hash運算,然後再去和保存在文件系統中的MD5值進行比較,進而確定輸入的密碼是否正確。通過這樣的步驟,系統在並不知道用戶密碼的明碼的情況下就可以確定用戶登錄系統的合法性。這可以避免用戶的密碼被具有系統管理員權限的用戶知道。MD5將任意長度的“字節串”映射爲一個128bit的大整數,並且是通過該128bit反推原始字符串是困難的,換句話說就是,即使你看到源程序和算法描述,也無法將一個MD5的值變換回原始的字符串,從數學原理上說,是因爲原始的字符串有無窮多個,這有點象不存在反函數的數學函數。所以,要遇到了md5密碼的問題,比較好的辦法是:你可以用這個系統中的md5()函數重新設一個密碼,如admin,把生成的一串密碼的Hash值覆蓋原來的Hash值就行了。
正是因爲這個原因,現在被黑客使用最多的一種破譯密碼的方法就是一種被稱爲"跑字典"的方法。有兩種方法得到字典,一種是日常蒐集的用做密碼的字符串表,另一種是用排列組合方法生成的,先用MD5程序計算出這些字典項的MD5值,然後再用目標的MD5值在這個字典中檢索。我們假設密碼的最大長度爲8位字節(8 Bytes),同時密碼只能是字母和數字,共26+26+10=62個字符,排列組合出的字典的項數則是P(62,1)+P(62,2)….+P(62,8),那也已經是一個很天文的數字了,存儲這個字典就需要TB級的磁盤陣列,而且這種方法還有一個前提,就是能獲得目標賬戶的密碼MD5值的情況下纔可以。這種加密技術被廣泛的應用於UNⅨ系統中,這也是爲什麼UNⅨ系統比一般操作系統更爲堅固一個重要原因。
算法原理
對MD5算法簡要的敘述可以爲:MD5以512位分組來處理輸入的信息,且每一分組又被劃分爲16個32位子分組,經過了一系列的處理後,算法的輸出由四個32位分組組成,將這四個32位分組級聯後將生成一個128位散列值。
關於MD5的解密
2004年8月17日的美國加州聖巴巴拉的國際密碼學會議(Crypto’2004)上,來自中國山東大學的王小云教授做了破譯MD5、HAVAL-128、 MD4和RIPEMD算法的報告,公佈了MD系列算法的破解結果。宣告了固若金湯的世界通行密碼標準MD5的堡壘轟然倒塌,引發了密碼學界的軒然大波。
需要指出的是,王小云教授研究出的破解方法,本質上並非真正的破解,只是加速了雜湊衝撞。通俗點說,知道一個MD5串,然後使用原文進行MD5散列後再生成同樣的MD5串,也就是說找到不同的原文產生相同MD5串的方法,這不是解密而稱之爲碰撞。MD5只有128位要真能解密的話,就變成一個超級壓縮工具了!
網絡上流行的MD5解密工具和在線解密網站,通常只是針對用戶密碼,或比較簡單的數字進行破解,其方法是將常用字符串的MD5密碼保存到數據庫,然後再與待解密的字符串做對比,最終找到匹配的源碼。
java實現MD5加密及密碼驗證
前面的論述告訴我們,MD5本身並沒有解密的算法,更談不上使用編程語言編寫解密算法了,下面給出java實現的MD5的加密以及用來做用戶密碼驗證的程序,僅供參考。
工具類MyMD5Util.java:
package com.zyg.security.md5;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
public class MyMD5Util {
private static final String HEX_NUMS_STR="0123456789ABCDEF";
private static final Integer SALT_LENGTH = 12;
/**
* 將16進制字符串轉換成字節數組
* @param hex
* @return
*/
public static byte[] hexStringToByte(String hex) {
int len = (hex.length() / 2);
byte[] result = new byte[len];
char[] hexChars = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (HEX_NUMS_STR.indexOf(hexChars[pos]) << 4
| HEX_NUMS_STR.indexOf(hexChars[pos + 1]));
}
return result;
}
/**
* 將指定byte數組轉換成16進制字符串
* @param b
* @return
*/
public static String byteToHexString(byte[] b) {
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
hexString.append(hex.toUpperCase());
}
return hexString.toString();
}
/**
* 驗證口令是否合法
* @param password
* @param passwordInDb
* @return
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public static boolean validPassword(String password, String passwordInDb)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
//將16進制字符串格式口令轉換成字節數組
byte[] pwdInDb = hexStringToByte(passwordInDb);
//聲明鹽變量
byte[] salt = new byte[SALT_LENGTH];
//將鹽從數據庫中保存的口令字節數組中提取出來
System.arraycopy(pwdInDb, 0, salt, 0, SALT_LENGTH);
//創建消息摘要對象
MessageDigest md = MessageDigest.getInstance("MD5");
//將鹽數據傳入消息摘要對象
md.update(salt);
//將口令的數據傳給消息摘要對象
md.update(password.getBytes("UTF-8"));
//生成輸入口令的消息摘要
byte[] digest = md.digest();
//聲明一個保存數據庫中口令消息摘要的變量
byte[] digestInDb = new byte[pwdInDb.length - SALT_LENGTH];
//取得數據庫中口令的消息摘要
System.arraycopy(pwdInDb, SALT_LENGTH, digestInDb, 0, digestInDb.length);
//比較根據輸入口令生成的消息摘要和數據庫中消息摘要是否相同
if (Arrays.equals(digest, digestInDb)) {
//口令正確返回口令匹配消息
return true;
} else {
//口令不正確返回口令不匹配消息
return false;
}
}
/**
* 獲得加密後的16進制形式口令
* @param password
* @return
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public static String getEncryptedPwd(String password)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
//聲明加密後的口令數組變量
byte[] pwd = null;
//隨機數生成器
SecureRandom random = new SecureRandom();
//聲明鹽數組變量
byte[] salt = new byte[SALT_LENGTH];
//將隨機數放入鹽變量中
random.nextBytes(salt);
//聲明消息摘要對象
MessageDigest md = null;
//創建消息摘要
md = MessageDigest.getInstance("MD5");
//將鹽數據傳入消息摘要對象
md.update(salt);
//將口令的數據傳給消息摘要對象
md.update(password.getBytes("UTF-8"));
//獲得消息摘要的字節數組
byte[] digest = md.digest();
//因爲要在口令的字節數組中存放鹽,所以加上鹽的字節長度
pwd = new byte[digest.length + SALT_LENGTH];
//將鹽的字節拷貝到生成的加密口令字節數組的前12個字節,以便在驗證口令時取出鹽
System.arraycopy(salt, 0, pwd, 0, SALT_LENGTH);
//將消息摘要拷貝到加密口令字節數組從第13個字節開始的字節
System.arraycopy(digest, 0, pwd, SALT_LENGTH, digest.length);
//將字節數組格式加密後的口令轉化爲16進制字符串格式的口令
return byteToHexString(pwd);
}
}
用於檢驗MD5加密及密碼驗證的測試類Client.java:
package com.zyg.security.md5;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
public class Client {
private static Map users = new HashMap();
public static void main(String[] args){
String userName = "zyg";
String password = "123";
registerUser(userName,password);
userName = "changong";
password = "456";
registerUser(userName,password);
String loginUserId = "zyg";
String pwd = "1232";
try {
if(loginValid(loginUserId,pwd)){
System.out.println("歡迎登陸!!!");
}else{
System.out.println("口令錯誤,請重新輸入!!!");
}
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 註冊用戶
*
* @param userName
* @param password
*/
public static void registerUser(String userName,String password){
String encryptedPwd = null;
try {
encryptedPwd = MyMD5Util.getEncryptedPwd(password);
users.put(userName, encryptedPwd);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 驗證登陸
*
* @param userName
* @param password
* @return
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
public static boolean loginValid(String userName,String password)
throws NoSuchAlgorithmException, UnsupportedEncodingException{
String pwdInDb = (String)users.get(userName);
if(null!=pwdInDb){ // 該用戶存在
return MyMD5Util.validPassword(password, pwdInDb);
}else{
System.out.println("不存在該用戶!!!");
return false;
}
}
}
以上內容主要由以下資源整理而得: