做MD5加密的目的
如果不做任何處理,那明文密碼就會在網絡上進行傳輸,假如在傳輸過程中被惡意用戶取得這個數據,就可以得到這個密碼,所以不安全
做兩次MD5加密的目的
前端:pwd=MD5(明文+固定salt)
後端:pwd=MD5(第一次加密後的密碼+隨機salt)
第一次 (在前端加密):密碼加密是(明文密碼+固定鹽值)生成MD5用於傳輸,目的是由於http是明文傳輸,當輸入密碼若直接發送到服務端進行驗證,此時如果被截取將可以直接獲取到明文密碼,獲取用戶信息;加鹽值是爲了混淆密碼,原則就是明文密碼不能在網絡上傳輸
第二次(在後端加密):服務端接收到已經用MD5加密的密碼後,我們並沒有直接存到數據庫裏面,而是生成一個隨機的salt,跟用戶輸入的密碼一起拼裝,再做一次MD5加密,然後再把得到的密碼存在數據庫裏面
第二次加密的目的:防止數據庫被入侵,被人通過彩虹表反查出密碼,所以服務端接受到密碼後,也不是直接寫入到數據庫,而是生成一個隨機鹽(salt),再進行一次MD5加密後存入數據庫
相關代碼與分析
使用兩次MD5加密生成用戶信息併入庫的代碼(後端模擬註冊):
package com.javaxl.miaosha_05.util;
import com.javaxl.miaosha_05.domain.MiaoshaUser;
import com.javaxl.miaosha_05.shiro.PasswordHelper;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class UserUtil {
private static void createUser(int count) throws Exception {
List<MiaoshaUser> users = new ArrayList<MiaoshaUser>(count);
//生成用戶
for (int i = 0; i < count; i++) {
//生成隨機鹽
String salt = PasswordHelper.createSalt();
MiaoshaUser user = new MiaoshaUser();
user.setId(15200000000L + i);
user.setLoginCount(0);
user.setNickname("user" + i);
user.setRegisterDate(new Date());
user.setSalt(salt);
//模擬將用戶輸入的密碼進行MD5(明文+固定Salt)加密後,再次進行MD5(第一次加密後的密碼+隨機Salt)加密
user.setPassword(PasswordHelper.createCredentials(MD5Util.inputPassToFormPass("123456"), salt));
users.add(user);
}
//插入數據庫
Connection conn = DBUtil.getConn();
String sql = "insert into miaosha_user(login_count, nickname, register_date, salt, password, id)values(?,?,?,?,?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
for (int i = 0; i < users.size(); i++) {
MiaoshaUser user = users.get(i);
pstmt.setInt(1, user.getLoginCount());
pstmt.setString(2, user.getNickname());
pstmt.setTimestamp(3, new Timestamp(user.getRegisterDate().getTime()));
pstmt.setString(4, user.getSalt());
pstmt.setString(5, user.getPassword());
pstmt.setLong(6, user.getId());
pstmt.addBatch();
}
pstmt.executeBatch();
pstmt.close();
conn.close();
}
public static void main(String[] args) throws Exception {
createUser(10);
}
}
數據庫裏面存的是做了兩次MD5加密後的用戶密碼與其對應的隨機salt值:
現在我們登錄的時候,要去取數據庫裏面對應用戶的密碼和salt值,後臺接收到前端做了一次MD5的密碼formPass,然後將這個formPass和數據庫裏面的salt一起再做一次MD5加密,然後看是否與數據庫裏面存的那個密碼一致,如果一致,則登錄成功,否則登錄失敗
注:第二次MD5加密所用的隨機salt爲什麼要保存在數據庫裏,當數據庫被侵入,做MD5加密之後的密碼和隨機salt一起被盜的話,用戶密碼不就泄露了嗎?
如果不保存這個隨機的salt,那麼用戶登錄的時候就沒法和數據庫保存的密碼進行校驗了實際上做MD5也不是絕對安全的,但是我們可以使得破解的難度呈指數型增長。MD5是不可逆的,不能反向解密的,網上所謂的“解密”都是把“加密”結果存儲到數據庫再比對的,只能暴力破解,即有一個字典,從字典中讀取一條記錄,將密碼用加salt鹽值做MD5加密來對比數據庫裏面的值是否相等
因爲好事者收集常用的密碼,然後對他們執行MD5加密,然後做成一個數據量非常龐大的數據字典,然後對泄露的數據庫中的密碼進行對比,如果你的原始密碼很不幸的被包含在這個數據字典中,那麼花不了多長時間就能把你的原始密碼匹配出來,這個數據字典很容易收集,假設有600w個密碼,壞人們可以利用他們數據字典中的密碼,加上我們泄露數據庫中的salt,然後散列再進行匹配
但是由於我們的salt是隨機產生的,每條數據都要加上salt後再散列,假如我們的用戶數據表中有30w條數據,數據字典中有 600w條數據,壞人們如果想要完全覆蓋的話,他們就必須加上salt後再散列,那數據字典數據量就應該是30w*600w,所以說幹壞事的成本也提高。但是如果只是想破解某個用戶的密碼的話,只需爲這600w條數據加上salt,然後散列匹配,可見salt雖然大大提高了安全係數,但也並非絕對安全
實際項目中,salt也不一定要加在最前面或最後面,也可以插在中間,也可以分開插入,也可以倒序,程序設計時可以靈活調整,都可以使破解的難度呈指數型增長
登錄邏輯處理:
前端代碼:
controller層代碼:
service層代碼:
其他幫助類代碼:
MD5Util.java代碼:
package com.javaxl.miaosha_05.util;
import org.apache.commons.codec.digest.DigestUtils;
public class MD5Util {
public static String md5(String src) {
return DigestUtils.md5Hex(src);
}
//客戶端固定的salt,跟用戶的密碼做一個拼裝
private static final String salt = "1a2b3c4d";
public static String inputPassToFormPass(String inputPass) {
String str = "" + salt.charAt(0) + salt.charAt(2) + inputPass + salt.charAt(5) + salt.charAt(4);
System.out.println(str);
return md5(str);
}
public static String formPassToDBPass(String formPass, String salt) {
String str = "" + salt.charAt(0) + salt.charAt(2) + formPass + salt.charAt(5) + salt.charAt(4);
return md5(str);
}
public static String inputPassToDbPass(String inputPass, String saltDB) {
String formPass = inputPassToFormPass(inputPass);
String dbPass = formPassToDBPass(formPass, saltDB);
return dbPass;
}
}
PasswordHelper.java代碼:
package com.javaxl.miaosha_05.shiro;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
/**
* 用於shiro權限認證的密碼工具類
*/
public class PasswordHelper {
/**
* 隨機數生成器
*/
private static RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
/**
* 指定hash算法爲MD5
*/
private static final String hashAlgorithmName = "md5";
/**
* 指定散列次數爲1024次,即加密1024次
*/
private static final int hashIterations = 1024;
/**
* true指定Hash散列值使用Hex加密存. false表明hash散列值用用Base64-encoded存儲
*/
private static final boolean storedCredentialsHexEncoded = true;
/**
* 獲得加密用的鹽
* @return
*/
public static String createSalt() {
return randomNumberGenerator.nextBytes().toHex();
}
/**
* 獲得加密後的憑證
* @param credentials:憑證(即密碼)
* @param salt:鹽
* @return
*/
public static String createCredentials(String credentials, String salt) {
SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials,salt, hashIterations);
return storedCredentialsHexEncoded ? simpleHash.toHex() : simpleHash.toBase64();
}
/**
* 進行密碼驗證
* @param credentials:未加密的密碼
* @param salt:鹽
* @param encryptCredentials:加密後的密碼
* @return
*/
public static boolean checkCredentials(String credentials, String salt, String encryptCredentials) {
return encryptCredentials.equals(createCredentials(credentials, salt));
}
}