Java實現(xiàn)加鹽算法的兩種方法
密碼安全是一件很重要的事情,所以一定要謹(jǐn)慎對待
常見的主要是3種方式
- 明文
- MD5加密
- 加鹽算法
首先明文肯定是不可取的,在數(shù)據(jù)庫中明文存儲密碼風(fēng)險實在是太大了
簡單來說,使用MD5就是將一串字符串通過某特定的算法來將其變成另一種形式,這樣子就在外觀上起到了加密的效果,但是由于背后的算法是固定的,所以每一個字符串都有固定的MD5格式
密碼破解程序可以是暴力破解:將得到的密碼使用MD5轉(zhuǎn)換成哈希,之后將得到的哈希與最初的哈希進行比較,要是匹配就說明已經(jīng)破解了密碼,但是這種方法的時間復(fù)雜度很高,會耗時很久
彩虹表:彩虹表記錄了幾乎所有字符串的MD5對照表
有了彩虹表MD5就相當(dāng)于是不存在了,因為一種字符串就只有一種特定的MD5格式
手寫一個加鹽算法
首先要理解“鹽”的概念,他就是一個隨機值,沒有任何規(guī)律
這里約定密碼的最終格式都是 鹽值(32位)$加密后的密碼(32位)
加密的實現(xiàn)思路:
每次調(diào)用的時候都會隨機生成一個鹽值(隨機、唯一) + 用戶輸入的密碼(使用MD5) = 加密的密碼,鹽值(32位) + $ + 加密密碼(32位) = 最終的密碼格式
解密(驗證密碼)的實現(xiàn)思路:
解密的時候需要兩個密碼 : 用戶輸入的明文待驗證密碼 、 存儲在數(shù)據(jù)庫中的最終密碼(自定義格式: 鹽值(32位)$加密后的密碼(32位))
解密(驗證密碼)的核心在于得到 鹽值
解密的時候,首先從最終數(shù)據(jù)庫中的密碼中來得到鹽值,之后將用戶輸入的明文待驗證密碼加上這個鹽值,生成加密后的密碼,然后使用鹽值 + 分隔符 + 加密后的密碼 生成 最終密碼格式,再與數(shù)據(jù)庫中最終的密碼格式進行比對
要是一樣的,那就說明這個用戶輸入的密碼是沒有問題的,要是不對就說明密碼輸入錯誤
最重要的是先理解加鹽 解密的實現(xiàn)思路,這是最核心的!??!
就算使用加鹽算法來對密碼加密,也不能保證就一定是安全的,可以針對一個鹽值來生成一個彩虹表,暴力破解也是可以的,但是這只是破解了一個賬號密碼,所以破解的成本是極大的,當(dāng)破解的成本遠大于收益的時候,可以看做是安全的
解密(驗證密碼)具體的實現(xiàn)步驟:
- 從數(shù)據(jù)庫中真正的最終密碼中得到鹽值
- 將用戶輸入的明文密碼+鹽值 = 加密后的密碼(使用MD5)
- 使用鹽值 + 分隔符 + 加密后的密碼 生成 最終密碼(最終密碼的格式)
- 對比生成的最終密碼和數(shù)據(jù)庫中的最終密碼是否相等
要是相等就說明用戶名和密碼都是對的,要是不對,就說明密碼輸入錯誤
為什么就是能驗證成功呢?
最終比對的就是三個部分: 鹽值 我就是從數(shù)據(jù)庫中的最終密碼中拿的前32位,肯定是一樣的,$都是一樣的,加密的部分都是MD5加密的,所以 也一定是一樣的,所以能登錄成功
具體的代碼:
在common包下面建一個PasswordUtils類
package com.example.demo.common; import org.springframework.util.DigestUtils; import org.springframework.util.StringUtils; import java.util.UUID; public class PasswordUtils{ /** * 1.加鹽并生成最終的密碼 * @param password 明文的密碼 * @return 最終生成的密碼 */ public static String encrypt(String password){ //a.產(chǎn)生鹽值 //UUID.randomUUID()會生成32位數(shù)字+4位-,是隨機的唯一的,將4位-去掉就得到32位數(shù)字的鹽值 String salt = UUID.randomUUID().toString().replace("-",""); //生成加鹽后的密碼(需要使用MD5) String saltPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes()); //生成最終的密碼格式 String finalPassword = salt + "$" + saltPassword; return finalPassword; } /** * 2.加鹽并生成最終密碼格式(方法一的重載),區(qū)別于上面的方法:這個方法是用來解密的,給定了鹽值,生成一個最終密碼, 后面要和正確的最終密碼進行比對 * @param password 需要驗證的明文密碼 * @param salt * @return */ public static String encrypt(String password, String salt){ //1.生成一個加密后的密碼 String saltPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes()); //2.生成最終的密碼(待驗證) String finalPassword = salt + "$" + saltPassword; return finalPassword; } /** * 3.驗證密碼 * @param inputPassword 登錄用戶輸入的明文密碼 * @param finalPassword 數(shù)據(jù)庫中實際的最終密碼格式 * @return */ public static boolean check(String inputPassword, String finalPassword){ //首先判斷這兩個參數(shù)到底有沒有值,數(shù)據(jù)庫中的最終密碼是不是65位 if(StringUtils.hasLength(inputPassword) && StringUtils.hasLength(finalPassword) && finalPassword.length() == 65){ //a.首先從最終的密碼中得到鹽值 //使用$將finalPassword劃分成兩個部分,前面的32位的部分就是鹽值 //注意:這里的$是被認為是一個通配符,所以要轉(zhuǎn)義一下 String salt = finalPassword.split("\\$")[0]; //b.使用之前加密的方法,生成最終的密碼格式(待驗證) String checkPassword = encrypt(inputPassword,salt); if(checkPassword.equals(finalPassword)){ return true; } } return false; } }
在寫完了加鹽算法之后,就要修改一下博客的具體調(diào)用了
在userinfoController中的注冊接口:
@RequestMapping("/reg") public AjaxResult reg(UserInfo userinfo) { //非空判斷 //雖然前端已經(jīng)進行了非空檢查,但是用戶可能會通過別的方式直接訪問url繞過前端的非空校驗,所以作為后端,應(yīng)該要考慮到這一點 //所以在后端也是要寫非空校驗的 if (userinfo == null || !StringUtils.hasLength(userinfo.getUsername()) || !StringUtils.hasLength(userinfo.getPassword())){ return AjaxResult.fail(-1,"非法參數(shù)"); } //不是空的話,就直接返回成功的響應(yīng)就行了 //這里響應(yīng)的是1,所以前端在進行成功判斷的時候才有result.data == 1這一條 //注冊成功之后要將密碼加鹽,寫進數(shù)據(jù)庫中 userinfo.setPassword(PasswordUtils.encrypt(userinfo.getPassword())); return AjaxResult.success(userService.reg(userinfo)); }
在userinfoController中的登錄接口:
@RequestMapping("/login") public AjaxResult login(HttpServletRequest request, String username, String password) { //1.進行非空判斷 if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){ //說明沒有傳入任何參數(shù) return AjaxResult.fail(-1,"非法請求"); } //2.查詢數(shù)據(jù)庫 UserInfo userinfo = userService.getUserByName(username); if (userinfo != null && userinfo.getId() > 0) { //能獲得id就說明用戶名一定是在數(shù)據(jù)庫中,說明是有效用戶 //使用自己寫的加鹽算法來判斷登錄 //password是輸入的待驗證的明文密碼,userinfo.getPassword()得到的是數(shù)據(jù)庫中正確的最終密碼格式 if (PasswordUtils.check(password,userinfo.getPassword())){ //將用戶的session存儲下來 //參數(shù)為true:要是沒有session就創(chuàng)建一個會話 HttpSession session = request.getSession(true); //設(shè)置session的key和value session.setAttribute(ApplicationVariable.USER_SESSION_KEY,userinfo); //要是密碼正確,在將數(shù)據(jù)返回之前,考慮到隱私,隱藏密碼 userinfo.setPassword(""); return AjaxResult.success(userinfo); } } return AjaxResult.fail(0,null); }
以上就是自己手寫的一個加鹽算法
實際上,springboot官方也提供了一種更加齊全的安全框架: spring security
spring security
要想使用spring security首先要先引入依賴(可以通過插件Edit Starters來引入依賴)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
在spring security框架中在項目啟動的時候,會自動注入登錄的頁面,在一般的項目中都是有自己的登錄頁面的,所以不需要自動注入登錄,所以要將其去掉
在項目的啟動類前面的@SpringBootApplication注解加上排除SecurityAutoConfiguration.class這個類對象就行了
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; //關(guān)閉spring security的驗證 @SpringBootApplication(exclude = SecurityAutoConfiguration.class) public class Demo3Application { public static void main(String[] args) { SpringApplication.run(Demo3Application.class, args); } }
在單元測試中使用spring security中的密碼加鹽算法
package com.example.demo; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @SpringBootTest class Demo3ApplicationTests { @Test void contextLoads() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String str = "111"; //進行加密 String finalPassword = bCryptPasswordEncoder.encode(str); System.out.println(finalPassword); //驗證密碼 String inputPassword1 = "123"; String inputPassword2 = "111"; //inputPassword是用戶輸入的密碼(待驗證),finalPassword是存儲在數(shù)據(jù)庫中的最終密碼格式 System.out.println(bCryptPasswordEncoder.matches(inputPassword1,finalPassword)); System.out.println(bCryptPasswordEncoder.matches(inputPassword2,finalPassword)); } }
加密之后的最終密碼格式: $2a 10 10 10BXpuKmotUdqoS3rFE59anOTrSfk7gCYX5wfsg9ZblBHvc79EyVFOi
spring security中的最終密碼的格式:
其實spring security的加鹽算法就是bCryptPasswordEncoder對象的encode方法和matches方法
加密的時候encode方法傳的參數(shù)是用戶輸入的密碼
解密(驗證)的時候,使用的matches方法的參數(shù)分別是用戶輸入的明文密碼 和 數(shù)據(jù)庫中最終密碼格式
所以自己手寫一個加鹽算法之后再去看spring security調(diào)用就會很簡單,因為思想都是一樣的,所以先理解實現(xiàn)思路很重要!
到此這篇關(guān)于Java實現(xiàn)加鹽算法的兩種方法的文章就介紹到這了,更多相關(guān)Java 加鹽算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java枚舉類的屬性、方法和構(gòu)造方法應(yīng)用實戰(zhàn)
這篇文章主要介紹了java枚舉類的屬性、方法和構(gòu)造方法應(yīng)用,結(jié)合實例形式分析了java枚舉類的定義、構(gòu)造及相關(guān)應(yīng)用操作技巧,需要的朋友可以參考下2019-08-08詳解Spring中Bean的生命周期和作用域及實現(xiàn)方式
這篇文章主要給大家介紹了Spring中Bean的生命周期和作用域及實現(xiàn)方式的相關(guān)資料,文中介紹的非常詳細,對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-03-03Java 中文字符按Unicode排序的實現(xiàn)方法
這篇文章主要介紹了Java 中文字符按Unicode排序的實現(xiàn)方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-10-10淺談Java之Map 按值排序 (Map sort by value)
下面小編就為大家?guī)硪黄獪\談Java之Map 按值排序 (Map sort by value)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08JavaWeb連接數(shù)據(jù)庫MySQL的操作技巧
數(shù)據(jù)庫是編程中重要的一部分,它囊括了數(shù)據(jù)操作,數(shù)據(jù)持久化等各方面。在每一門編程語言中都占有相當(dāng)大的比例。本次,小編以MySQL為例,使用mvc編程思想,給大家講解下javaweb對數(shù)據(jù)庫的操作2017-02-02