對儲存在資料庫中的密碼進行解密操作:
可以看到成功將我的密碼解密出來,這讓我很吃驚,因為我們都知道MD5算法是不可逆的,因為它是其是一種散列函數,使用的是hash算法,在計算過程中原文的部分信息是丟失了的。那為什麼網站中可以將我的密碼解密出來呢?
經過一番查找後發現,原來線上解密工具的解密原理很簡單,其原理是收集使用者常用的簡單密碼形成了一個密碼字典,並將字典中的密碼用MD5加密後儲存起來,在所謂的「解密「的時候,就將真正用戶密碼加密都的密文與已儲存的密碼相比較,如該密文存在於字典當中,即可以「解密」。因此,簡單的用MD5對使用者密碼加密還不安全,我們設定密碼時候也通常都會有複雜度偵測,像是密碼必須帶英文數字什麼的,就是為了安全性考量。
知道其解密原理之後,我們嘗試一下複雜點的密碼看看能不能「解密」出來,首先對密碼「qweasd666」進行加密:
加密之後我們選取32位元小寫的密文進行解密運算:
#解密失敗顯示其原理是透過收集常用的密文進行配對破解。既然「qweasd666」密碼無法被破解,那麼它應該是安全的,你們都可以使用這個密碼(doge)。
言歸正傳,說到這個網站成功將我精心設定的密碼給破解了,這讓我很沒有安全感,而且我感覺我很沒有面子,我一定要將我的登入安全等級進行提升。
除了上面提到的解密操作之外,還有一個很大的問題就是在前端將資料傳輸過來時候http採用的是明文傳輸,如果傳輸資料包被截取,那麼就算你後端的加密演算法有多複雜,你的密碼也會被別人知道。
找到問題所在之後,我們就可以對症下藥了,首先我覺得要解決的是http明文傳輸問題,因為這個風險最大,普通抓包就能抓取密碼,這不是很恐怖的一件事嗎。既然明文傳輸有安全問題,我們可以透過加密來確保傳輸安全性
我仍然採用MD5加密方案,但在對密碼加密前,增加了一個固定的salt值。 salt?是加鹽嗎,其實差不多,不如說是加點「佐料」。其基本想法是這樣的:當用戶首次提供密碼時(通常是註冊時),由程式往這個密碼裡撒一些“佐料”,為了減輕開發壓力,這個佐料對於每一個用戶都是相同的,然後再散列。這樣就能防止傳輸過程中出現明文密碼外洩。
注意上面我提到的是防止明文密碼洩漏,但這並不代表密文不會洩漏,假如駭客截取你透過http傳輸的經過加密後的密碼,或是資料庫發生洩漏導致加密後的密碼被駭客盜取,駭客透過解析前端js檔案來獲得前端固定鹽值,那麼駭客可能可以透過彩虹表進行反向查詢得到原始密碼。這時候就二次加密的重要性就出來了,二次加密實現原理為對前端傳過來經過加密之後的密碼再次和一個隨機Salt值結合後進行加密(注意這次是隨機的),鹽值會在用戶登陸的時候隨機生成,並存在資料庫中。
這時候可能你會跟我剛開始一樣也會有一個疑惑,那就是你把隨機鹽值保存到資料庫中了,那麼如果資料庫洩漏那你的隨機鹽值還有什麼作用呢?駭客拿到加密資料解密後移除鹽值不是就能得到密碼了嗎?是的,駭客有可能透過這樣的手段得到密碼,但是前提是他能解密出來,要知道的是MD5是無法直接破解的,只能透過窮舉法解密。就算駭客拿到了資料庫中的加密密碼,但是不知道後端的加密過程,他就無法進行解析,就算駭客同時知道加密過程,由於經過了二次加鹽二次加密,這時候的密文是很難很難被解析出來的,隨著加密資料的複雜度增加,破解成本是呈指數級增加的,在那麼大的成本面前相信沒有什麼駭客願意去嘗試。當然,salt也不一定要加在最前面或最後面,也可以插在中間,也可以分開插入,也可以倒序,程式設計時可以靈活調整,都可以使破解的難度呈指數型增長。
2.3:具體實作
2.3.1:使用者註冊
#前端對使用者輸入的密碼進行md5加密(固定的salt)
將加密後的密碼傳遞到後端
#後端隨機產生一個salt
使用產生salt將前端傳過來的密碼加密,然後將加密後密碼和salt一起儲存到db
2.3.2:使用者登入
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加密怎麼實現安全登入的詳細內容。更多資訊請關注PHP中文網其他相關文章!