加鹽算法
密碼安全是一件很重要的事情,所以一定要謹慎對待
常見的主要是3種方式
- 明文
- MD5加密
- 加鹽算法
首先明文肯定是不可取的,在數(shù)據(jù)庫中明文存儲密碼風險實在是太大了
簡單來說,使用MD5就是將一串字符串通過某特定的算法來將其變成另一種形式,這樣子就在外觀上起到了加密的效果,但是由于背后的算法是固定的,所以每一個字符串都有固定的MD5格式
密碼破解程序可以是暴力破解:將得到的密碼使用MD5轉換成哈希,之后將得到的哈希與最初的哈希進行比較,要是匹配就說明已經(jīng)破解了密碼,但是這種方法的時間復雜度很高,會耗時很久
彩虹表:彩虹表記錄了幾乎所有字符串的MD5對照表
有了彩虹表MD5就相當于是不存在了,因為一種字符串就只有一種特定的MD5格式
手寫一個加鹽算法
首先要理解“鹽”的概念,他就是一個隨機值,沒有任何規(guī)律
這里約定密碼的最終格式都是 鹽值(32位)$加密后的密碼(32位)
加密的實現(xiàn)思路:
每次調用的時候都會隨機生成一個鹽值(隨機、唯一) + 用戶輸入的密碼(使用MD5) = 加密的密碼,鹽值(32位) + $ + 加密密碼(32位) = 最終的密碼格式
解密(驗證密碼)的實現(xiàn)思路:
解密的時候需要兩個密碼 : 用戶輸入的明文待驗證密碼 、 存儲在數(shù)據(jù)庫中的最終密碼(自定義格式: 鹽值(32位)$加密后的密碼(32位))
解密(驗證密碼)的核心在于得到 鹽值
解密的時候,首先從最終數(shù)據(jù)庫中的密碼中來得到鹽值,之后將用戶輸入的明文待驗證密碼加上這個鹽值,生成加密后的密碼,然后使用鹽值 + 分隔符 + 加密后的密碼 生成 最終密碼格式,再與數(shù)據(jù)庫中最終的密碼格式進行比對
要是一樣的,那就說明這個用戶輸入的密碼是沒有問題的,要是不對就說明密碼輸入錯誤
最重要的是先理解加鹽 解密的實現(xiàn)思路,這是最核心的!?。?/p>
就算使用加鹽算法來對密碼加密,也不能保證就一定是安全的,可以針對一個鹽值來生成一個彩虹表,暴力破解也是可以的,但是這只是破解了一個賬號密碼,所以破解的成本是極大的,當破解的成本遠大于收益的時候,可以看做是安全的
解密(驗證密碼)具體的實現(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位的部分就是鹽值
//注意:這里的$是被認為是一個通配符,所以要轉義一下
String salt = finalPassword.split("\\$")[0];
//b.使用之前加密的方法,生成最終的密碼格式(待驗證)
String checkPassword = encrypt(inputPassword,salt);
if(checkPassword.equals(finalPassword)){
return true;
}
}
return false;
}
}
在寫完了加鹽算法之后,就要修改一下博客的具體調用了
在userinfoController中的注冊接口:
@RequestMapping("/reg")
public AjaxResult reg(UserInfo userinfo) {
//非空判斷
//雖然前端已經(jīng)進行了非空檢查,但是用戶可能會通過別的方式直接訪問url繞過前端的非空校驗,所以作為后端,應該要考慮到這一點
//所以在后端也是要寫非空校驗的
if (userinfo == null || !StringUtils.hasLength(userinfo.getUsername()) ||
!StringUtils.hasLength(userinfo.getPassword())){
return AjaxResult.fail(-1,"非法參數(shù)");
}
//不是空的話,就直接返回成功的響應就行了
//這里響應的是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);
//設置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;
//關閉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ù)庫中最終密碼格式文章來源:http://www.zghlxwxcb.cn/news/detail-411759.html
所以自己手寫一個加鹽算法之后再去看spring security調用就會很簡單,因為思想都是一樣的,所以先理解實現(xiàn)思路很重要!文章來源地址http://www.zghlxwxcb.cn/news/detail-411759.html
到了這里,關于密碼加密——加鹽算法(兩種方式)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!