【商城秒殺項目】-- 登錄時使用兩次MD5加密

做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));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章