Java雙重MD5加密實現(xiàn)安全登錄
一:問題引入
今天看到一篇文章說使用MD5對密碼進行加密存儲也還不能做到很安全,網(wǎng)上有在線解密MD5的網(wǎng)站,我一搜,還真有。接下來我嘗試對我存儲在數(shù)據(jù)庫中的密碼進行解密操作:


可以看到成功將我的密碼解密出來,這讓我很吃驚,因為我們都知道MD5算法是不可逆的,因為它是其是一種散列函數(shù),使用的是hash算法,在計算過程中原文的部分信息是丟失了的。那么為什么網(wǎng)站中可以將我的密碼解密出來呢?
經(jīng)過一番查找后發(fā)現(xiàn),原來在線解密工具的解密原理很簡單,其原理是收集用戶常用的簡單密碼形成了一個密碼字典,并將字典中的密碼用MD5加密后存儲起來,在所謂的“解密“的時候,就將真正用戶密碼加密都的密文與已存儲的密碼相比較,如該密文存在于字典當中,即可以“解密”。因此,簡單的用MD5對用戶密碼加密還不安全,我們設置密碼時候也通常都會有復雜度檢測,比如密碼必須帶英文數(shù)字什么的,就是為了安全性考慮。
知道其解密原理之后,我們嘗試一下復雜點的密碼看看能不能“解密”出來,首先對密碼“qweasd666”進行加密:

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

可以看到解密失敗,說明其原理就是我們上面所說的收集常用的密文進行一一對應。既然解密失敗了,那么說明“qweasd666”這個密碼是安全的,你們可以都設置這個密碼(doge)。
言歸正傳,說到這個網(wǎng)站成功將我精心設置的密碼給破解了,這讓我很沒有安全感,而且我感覺我很沒有面子,我一定要將我的登錄安全等級進行提升。
除了上面提到的解密操作之外,還有一個很大的問題就是在前端將數(shù)據(jù)傳輸過來時候http采用的是明文傳輸,如果傳輸數(shù)據(jù)包被截取,那么就算你后端的加密算法有多復雜,你的密碼也會被別人知道。
二:解決方案
2.1:第一次加密
找到問題所在之后,我們就可以對癥下藥了,首先我覺得要解決的是http明文傳輸問題,因為這個風險最大,普通抓包就能抓取密碼,這不是很恐怖的一件事嗎。解決方案也很簡單,既然明文傳輸不安全,那么我們加密后再進行傳輸不就行了嗎?
我選用的方案仍然還是MD5加密,但是對密碼加密前還要加入一個固定salt。salt?是加鹽嗎,其實差不多,更不如說是加點“佐料”。其基本想法是這樣的:當用戶首次提供密碼時(通常是注冊時),由程序往這個密碼里撒一些“佐料”,為了減輕開發(fā)壓力,這個佐料對于每一個用戶都是相同的,然后再散列。這樣就能防止傳輸過程中出現(xiàn)明文密碼泄露。
2.2:第二次加密
注意上面我提到的是防止出現(xiàn)明文密碼泄漏,但是這并不代表密文不會泄漏,假如黑客截取你通過http傳輸?shù)慕?jīng)過加密后的密碼,或者數(shù)據(jù)庫發(fā)生泄漏導致加密后的密碼被黑客盜取,黑客通過解析前端js文件獲得前端固定鹽值,那么黑客可能可以通過彩虹表進行反向查詢得到原始密碼。這時候就二次加密的重要性就出來了,二次加密實現(xiàn)原理為對前端傳過來經(jīng)過加密之后的密碼再次和一個隨機Salt值結(jié)合后進行加密(注意這次是隨機的),鹽值會在用戶登陸的時候隨機生成,并存在數(shù)據(jù)庫中。
這時候可能你會和我剛開始一樣也會有一個疑惑,那就是你把隨機鹽值保存到數(shù)據(jù)庫中了,那么如果數(shù)據(jù)庫泄漏那你的隨機鹽值還有什么作用呢?黑客拿到加密數(shù)據(jù)進行解密后去除鹽值不是就能得到密碼了嗎?是的,黑客有可能通過這樣的手段得到密碼,但是前提是他能解密出來,要知道的是MD5是無法直接破解的,只能通過窮舉法進行解密。就算黑客拿到了數(shù)據(jù)庫中的加密密碼,但是不知道后端的加密過程,他就無法進行解析,就算黑客同時知道加密過程,由于經(jīng)過了二次加鹽二次加密,這時候的密文是很難很難被解析出來的,隨著加密數(shù)據(jù)的復雜度增加,破解成本是呈指數(shù)級增加的,在那么大的成本面前相信沒有什么黑客愿意去嘗試。當然,salt也不一定要加在最前面或最后面,也可以插在中間,也可以分開插入,也可以倒序,程序設計時可以靈活調(diào)整,都可以使破解的難度呈指數(shù)型增長。
2.3:具體實現(xiàn)
2.3.1:用戶注冊
- 前端對用戶輸入的密碼進行md5加密(固定的salt)
- 將加密后的密碼傳遞到后端
- 后端隨機生成一個salt
- 使用生成salt對前端傳過來的密碼進行加密,然后將加密后密碼和salt一起保存到db中
2.3.2:用戶登錄
- 前端對用戶輸入的密碼進行md5加密(固定的salt)
- 將加密后的密碼傳遞到后端
- 后端使用用戶賬號取出用戶信息
- 后端對加密后的密碼在進行md5加密(取出鹽),然后與數(shù)據(jù)庫中存儲的密碼進行對比
- 匹配則登錄成功,否則登錄失敗
三:代碼實現(xiàn)
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,
//登錄密碼加上固定鹽值后發(fā)送
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.從數(shù)據(jù)庫中獲取用戶信息
LambdaQueryWrapper<Employee> lqw = new LambdaQueryWrapper<>();
lqw.eq(Employee::getUsername,employee.getUsername());
Employee emp = employeeService.getOne(lqw);
//3.如果沒有查詢到則返回登陸失敗查詢結(jié)果
if(emp == null){
return R.error("沒有查詢到該用戶信息!");
}
//4.獲取注冊時保存的隨機鹽值
String salt = emp.getSalt();
//5.將頁面提交的密碼password進行md5二次加密
String password = DigestUtils.md5DigestAsHex((salt + encryPassword).getBytes());
//6.密碼比對,如果不一致則返回登陸失敗結(jié)果
if(!emp.getPassword().equals(password)){
return R.error("密碼錯誤!");
}
//7.查看員工狀態(tài)是否可用
if(emp.getStatus() == 0){
return R.error("該員工已被禁用!");
}
//8.登錄成功,將員工id存入Session對象并返回登錄成功結(jié)果
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}到此這篇關(guān)于Java雙重MD5加密實現(xiàn)安全登錄的文章就介紹到這了,更多相關(guān)Java雙重MD5加密 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java底層AQS實現(xiàn)類ReentrantLock鎖的構(gòu)成及源碼解析
本章我們就要來學習一下第一個?AQS?的實現(xiàn)類:ReentrantLock,看看其底層是如何組合?AQS?,實現(xiàn)了自己的那些功能,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-03-03
關(guān)于ZooKeeper的會話機制Session解讀
這篇文章主要介紹了關(guān)于ZooKeeper的會話機制Session解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02
SpringBoot+Apache tika實現(xiàn)文檔內(nèi)容解析的示例詳解
Apache tika是Apache開源的一個文檔解析工具,本文主要為大家介紹了如何在springboot中引入tika的方式解析文檔,感興趣的小伙伴可以了解一下2023-07-07
java中break和continue區(qū)別及使用場合分析
本文力圖通過實例加使用場合詳解來引導菜鳥重新認識break和continue語句,需要的朋友可以參考下2014-01-01
Java web.xml之contextConfigLocation作用案例詳解
這篇文章主要介紹了Java web.xml之contextConfigLocation作用案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08
解決springboot 2.x集成log4j2調(diào)試日志無法關(guān)閉的問題
這篇文章主要介紹了解決springboot 2.x集成log4j2調(diào)試日志無法關(guān)閉的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Java實現(xiàn)微信小程序加密數(shù)據(jù)解密算法
我們開發(fā)微信小程序的過程中,我們的服務端有時需要獲取微信提供的開放數(shù)據(jù)。微信會對這些開放數(shù)據(jù)做簽名和加密處理,本文通過實例代碼給大家介紹Java實現(xiàn)微信小程序加密數(shù)據(jù)解密算法,感興趣的朋友一起看看吧2021-11-11
Springboot 實現(xiàn)數(shù)據(jù)庫備份還原的方法
這篇文章主要介紹了Springboot 實現(xiàn)數(shù)據(jù)庫備份還原的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09

