密碼和Java中的加解密之MD5加點鹽

    

很多人都用MD5+Base64方式存儲密碼,這種存儲方式 方便、速度快而且由於MD5雜湊算法的幾乎不可還原性,攻擊者只能通過"猜"去破解密碼。


但是MD5對相同的數據返回的信息永遠是一樣的,"123456"通過MD5+Base64編碼後,永遠是"4QrcOUm6Wau+VuBX8g+IPg==",攻擊者只需要一個簡單的sql語句:select * from userInfo where password='4QrcOUm6Wau+VuBX8g+IPg==' 就可以知道有幾個用戶密碼是"123456",這對一個項目來說十分危險。

//這個循環無論執行幾次,返回都是一樣。
    static void t3(){
        for(int i=0;i<5;i++){
            try {
                String inputPwd = "123456";
                MessageDigest md;
                BASE64Encoder b64Encoder = new BASE64Encoder();
                byte[] bys = null;
                
                md = MessageDigest.getInstance("MD5","SUN");
                md.reset();
                md.update(inputPwd.getBytes("UTF-8"));
                bys = md.digest();
                
                System.out.println("編碼後:"+b64Encoder.encode(bys));            
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

一種簡單有效的解決方法,就是在MD5雜湊前加點隨機碼,這樣攻擊者將無法通過一個簡單的SQL語句來查找密碼是"123456"的用戶。

隨機碼長度是應該固定,且同用戶輸入的密碼一起進行MD5雜湊,隨機碼應該在Base64編碼後的固定位置。

校驗時,先取出隨機碼,再同輸入一起進行MD5雜湊,與以前存儲的密碼比較是否一致。

package test1;

import java.security.MessageDigest;
import java.util.Random;

import sun.misc.BASE64Encoder;

/**
 * 對帳號口令加密
 * @author shanl
 *
 */
public class PasswdEncryption {
	private static final MessageDigest md;
	private static final BASE64Encoder b64Encoder;
	
	static{
		try {
			md = MessageDigest.getInstance("MD5", "SUN");
			b64Encoder = new BASE64Encoder();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 檢查密碼
	 * @param inputPasswd 用戶輸入的密碼
	 * @param storePasswd 已存儲的密碼
	 * @return true:通過檢查,false:未通過
	 */
	synchronized
	public static boolean checkPasswd(String inputPasswd, String storePasswd){
		boolean ok = false;
		
		try{
			byte[] saltBys = storePasswd.substring(0,2).getBytes("UTF-8");
			String inPwd = toPasswd(inputPasswd, saltBys);
			ok = inPwd.equals(storePasswd);
		}catch(Exception ex){
			ex.printStackTrace();
			ok = false;
		}
		
		return ok;
	}
	
	/**
	 * 將客戶輸入的密碼加密
	 * @param inputPasswd
	 * @return
	 */
	synchronized 
	public static String toPasswd(String inputPasswd){
		byte[] salt = getSaltOfASCII(2);
		return toPasswd(inputPasswd, salt);
	}
	
	/**
	 * 將客戶輸入的密碼加密
	 * @param inputPasswd 客戶輸入的密碼 
	 * @param salt 鹽
	 * @return 加密後的字符串
	 */
	synchronized
	private static String toPasswd(String inputPasswd, byte[] salt){
		String pwd = "";
		
		try{			
			md.reset();
			md.update(salt);
			md.update(inputPasswd.getBytes("UTF-8"));
			byte[] bys = md.digest();;
			//1:AASLexNtFtI7e1IuQIg88ZNA==
			//879:AA/lCM5NEwVQJ25YYomE1ldQ==
			//1000:AARXKQat7z+/iu2w6KpKgLQA==
//			for(int i=0; i<1000; i++){
//				md.reset();
//				bys = md.digest(bys);
//			}
						
			pwd += (char)salt[0];
			pwd += (char)salt[1];
			pwd += b64Encoder.encode(bys);			
		}catch(Exception ex){
			ex.printStackTrace();
			pwd = "";
		}
		
		return pwd;
	}
	
	/**
	 * 返回指定長度的鹽(ASCII碼)
	 * @param len 長度
	 * @return
	 */
	private static byte[] getSaltOfASCII(int len){
		byte[] salt = new byte[len];
		Random rand = new Random();
				
		for(int i=0; i<len; i++){
//			salt[i] = 'A';
			salt[i] = (byte) ((rand.nextInt('~'-'!')+'!') & 0x007f);
		}
				
		return salt;
	}
}

 

測試類:

package test1;

public class Test8 {
	public static void main(String[] args){
		t2();
//		t1();
	}
	
	static void t2(){
		String[] userPwds = {
				"123456",
				"123456",
				"123456",
				"123456",
				"123456",
				"1234561", //error input
		};
		
		//通過t1()得到
		String[] dbPwds = {
				"u:rkoqEBZ+IZlcugSyVkhNVg==",
				"vDEbVZ9JlSveGnB/Qdz69aAQ==",
				"hbgon/TjwH8s7YSuqVSDl9Yg==",
				"0-ExML8oTOSCeF3ETf1yvDHg==",
				"1TeHpWiXOhx5ilscC6D6G98g==",
				"1TeHpWiXOhx5ilscC6D6G98g==",
		};
		
		String msg = "";
		
		for(int i=0,end=dbPwds.length; i<end; i++){
			msg  = "user input passwd:"+userPwds[i];
			msg += ",db store passwd:"+dbPwds[i];
			msg += ",check passwd:"+PasswdEncryption.checkPasswd(userPwds[i],dbPwds[i]);
			
			System.out.println(msg);
		}
	}
	
	/**
	 * 加密測試
	 */
	static void t1(){
		String pwd = "123456";
		String pwd1 = null;//PasswdEncryption.toPasswd(pwd);
		
		for(int i=0; i<5; i++){
			pwd1 = PasswdEncryption.toPasswd(pwd);
			
			System.out.println(pwd1);
		}
	}
}

t1()結果顯示,在很小的情況下會出現相同的加密後密碼,攻擊者很難用一個簡單SQL去查找同一密碼的用戶。

t2()則可以用同一個密碼"123456"驗證,多個加密後的密碼。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章