Java雙重MD5加密實現安全登錄

一:問題引入

今天看到一篇文章說使用MD5對密碼進行加密存儲也還不能做到很安全,網上有在線解密MD5的網站,我一搜,還真有。接下來我嘗試對我存儲在數據庫中的密碼進行解密操作:

fb3013eaa3354b509420113da9d5afe7.png

837987e7457a4e1186c4dcf916dcb66e.png

 可以看到成功將我的密碼解密出來,這讓我很吃驚,因為我們都知道MD5算法是不可逆的,因為它是其是一種散列函數,使用的是hash算法,在計算過程中原文的部分信息是丟失瞭的。那麼為什麼網站中可以將我的密碼解密出來呢?

經過一番查找後發現,原來在線解密工具的解密原理很簡單,其原理是收集用戶常用的簡單密碼形成瞭一個密碼字典,並將字典中的密碼用MD5加密後存儲起來,在所謂的“解密“的時候,就將真正用戶密碼加密都的密文與已存儲的密碼相比較,如該密文存在於字典當中,即可以“解密”。因此,簡單的用MD5對用戶密碼加密還不安全,我們設置密碼時候也通常都會有復雜度檢測,比如密碼必須帶英文數字什麼的,就是為瞭安全性考慮。

知道其解密原理之後,我們嘗試一下復雜點的密碼看看能不能“解密”出來,首先對密碼“qweasd666”進行加密:

5e98f258fa3643fa974ca931577d4e1f.png

 加密之後我們選取32位小寫的密文進行解密操作:

b95fa649d9234f839734b42c8fd464ce.png

 可以看到解密失敗,說明其原理就是我們上面所說的收集常用的密文進行一一對應。既然解密失敗瞭,那麼說明“qweasd666”這個密碼是安全的,你們可以都設置這個密碼(doge)。

言歸正傳,說到這個網站成功將我精心設置的密碼給破解瞭,這讓我很沒有安全感,而且我感覺我很沒有面子,我一定要將我的登錄安全等級進行提升。

除瞭上面提到的解密操作之外,還有一個很大的問題就是在前端將數據傳輸過來時候http采用的是明文傳輸,如果傳輸數據包被截取,那麼就算你後端的加密算法有多復雜,你的密碼也會被別人知道。

二:解決方案

2.1:第一次加密

找到問題所在之後,我們就可以對癥下藥瞭,首先我覺得要解決的是http明文傳輸問題,因為這個風險最大,普通抓包就能抓取密碼,這不是很恐怖的一件事嗎。解決方案也很簡單,既然明文傳輸不安全,那麼我們加密後再進行傳輸不就行瞭嗎?

我選用的方案仍然還是MD5加密,但是對密碼加密前還要加入一個固定salt。salt?是加鹽嗎,其實差不多,更不如說是加點“佐料”。其基本想法是這樣的:當用戶首次提供密碼時(通常是註冊時),由程序往這個密碼裡撒一些“佐料”,為瞭減輕開發壓力,這個佐料對於每一個用戶都是相同的,然後再散列。這樣就能防止傳輸過程中出現明文密碼泄露。

2.2:第二次加密

註意上面我提到的是防止出現明文密碼泄漏,但是這並不代表密文不會泄漏,假如黑客截取你通過http傳輸的經過加密後的密碼,或者數據庫發生泄漏導致加密後的密碼被黑客盜取,黑客通過解析前端js文件獲得前端固定鹽值,那麼黑客可能可以通過彩虹表進行反向查詢得到原始密碼。這時候就二次加密的重要性就出來瞭,二次加密實現原理為對前端傳過來經過加密之後的密碼再次和一個隨機Salt值結合後進行加密(註意這次是隨機的),鹽值會在用戶登陸的時候隨機生成,並存在數據庫中。

這時候可能你會和我剛開始一樣也會有一個疑惑,那就是你把隨機鹽值保存到數據庫中瞭,那麼如果數據庫泄漏那你的隨機鹽值還有什麼作用呢?黑客拿到加密數據進行解密後去除鹽值不是就能得到密碼瞭嗎?是的,黑客有可能通過這樣的手段得到密碼,但是前提是他能解密出來,要知道的是MD5是無法直接破解的,隻能通過窮舉法進行解密。就算黑客拿到瞭數據庫中的加密密碼,但是不知道後端的加密過程,他就無法進行解析,就算黑客同時知道加密過程,由於經過瞭二次加鹽二次加密,這時候的密文是很難很難被解析出來的,隨著加密數據的復雜度增加,破解成本是呈指數級增加的,在那麼大的成本面前相信沒有什麼黑客願意去嘗試。當然,salt也不一定要加在最前面或最後面,也可以插在中間,也可以分開插入,也可以倒序,程序設計時可以靈活調整,都可以使破解的難度呈指數型增長。

2.3:具體實現

2.3.1:用戶註冊

  • 前端對用戶輸入的密碼進行md5加密(固定的salt)
  • 將加密後的密碼傳遞到後端
  • 後端隨機生成一個salt
  • 使用生成salt對前端傳過來的密碼進行加密,然後將加密後密碼和salt一起保存到db中

2.3.2:用戶登錄

  • 前端對用戶輸入的密碼進行md5加密(固定的salt)
  • 將加密後的密碼傳遞到後端
  • 後端使用用戶賬號取出用戶信息
  • 後端對加密後的密碼在進行md5加密(取出鹽),然後與數據庫中存儲的密碼進行對比
  • 匹配則登錄成功,否則登錄失敗

三:代碼實現

3.1:第一次加密

3.1.1:前端

const params = {
    ...this.ruleForm,
    sex: this.ruleForm.sex === '女' ? '0' : '1',
    //設置密碼加密(加上固定salt值)
    password: md5(this.ruleForm.password + this.salt)
}
addEmployee(params).then(res => {
    if (res.code === 1) {
        this.$message.success('員工添加成功!')
        if (!st) {
            this.goBack()
        } else {
            this.ruleForm = {
                username: '',
                'name': '',
                'phone': '',
                password: '',
                // 'rePassword': '',/
                'sex': '男',
                'idNumber': ''
            }
        }
    } else {
        this.$message.error(res.msg || '操作失敗')
    }
}

3.1.2:後端

/**
* 添加員工
*/
@PostMapping
public R<String> save(@RequestBody Employee employee){
    //生成隨機salt值
    String salt = RandomStringUtils.randomAlphanumeric(5);
    //設置隨機鹽值
    employee.setSalt(salt);
    //設置密碼二次加密
    employee.setPassword(DigestUtils.md5DigestAsHex((salt + employee.getPassword()).getBytes()));
 
    boolean save = employeeService.save(employee);
    if(save){
        return R.success("添加成功!");
    }
    return R.error("添加失敗!");
}

3.2:第二次加密

3.2.1:前端

const params = {
    ...this.loginForm,
    //登錄密碼加上固定鹽值後發送
    password: md5(this.loginForm.password + this.salt),
}
let res = await loginApi(params)
if (String(res.code) === '1') {
    localStorage.setItem('userInfo', JSON.stringify(res.data))
    window.location.href = '/backend/index.html'
} else {
    this.$message.error(res.msg)
    this.loading = false
}

3.2.2:後端

/**
 * 員工登錄
 */
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
    //1.獲取傳輸過來的加密後的密碼值
    String encryPassword = employee.getPassword();
 
    //2.從數據庫中獲取用戶信息
    LambdaQueryWrapper<Employee> lqw = new LambdaQueryWrapper<>();
    lqw.eq(Employee::getUsername,employee.getUsername());
    Employee emp = employeeService.getOne(lqw);
 
    //3.如果沒有查詢到則返回登陸失敗查詢結果
    if(emp == null){
        return R.error("沒有查詢到該用戶信息!");
    }
 
    //4.獲取註冊時保存的隨機鹽值
    String salt = emp.getSalt();
 
    //5.將頁面提交的密碼password進行md5二次加密
    String password = DigestUtils.md5DigestAsHex((salt + encryPassword).getBytes());
 
    //6.密碼比對,如果不一致則返回登陸失敗結果
    if(!emp.getPassword().equals(password)){
        return R.error("密碼錯誤!");
    }
 
    //7.查看員工狀態是否可用
    if(emp.getStatus() == 0){
        return R.error("該員工已被禁用!");
    }
    
    //8.登錄成功,將員工id存入Session對象並返回登錄成功結果
    request.getSession().setAttribute("employee",emp.getId());
    return R.success(emp);
}

到此這篇關於Java雙重MD5加密實現安全登錄的文章就介紹到這瞭,更多相關Java雙重MD5加密 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: