BCryptPasswordEncoder加密與MD5加密


參考文章:https://blog.csdn.net/weixin_42531204/article/details/105254213

一、使用BcryptPasswordEncoder

spring security中的BCryptPasswordEncoder方法采用SHA-256 +隨機鹽+密鑰對密碼進行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(這個與編碼/解碼一樣),但是采用Hash處理,其過程是不可逆的。

1、加密(encode)

if (user.getPassword() != null) {
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            user.setPassword(encoder.encode(user.getPassword().trim()));
        }

encode方法的源碼

public String encode(CharSequence rawPassword) {
        String salt;
        if (this.strength > 0) {
            if (this.random != null) {
                salt = BCrypt.gensalt(this.strength, this.random);
            } else {
                salt = BCrypt.gensalt(this.strength);
            }
        } else {
            salt = BCrypt.gensalt();
        }

        return BCrypt.hashpw(rawPassword.toString(), salt);
    }

注意:每次加密后的密碼都不一樣,每次的隨機鹽都保存在加密后的密碼中。在比較的時候,隨機鹽重新被取出。即加密后的密碼中,前部分已經包含了鹽信息。

2、密碼匹配(matches)

Result rs = new Result();
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        if (!encoder.matches(user.getPassword(), curUser.getPassword())) {
            rs.setSuccess(false);
            rs.setMsg("舊密碼錯誤!");
            rs.setCode(ResultCode.SUCCESS);
            return rs;
        }

用戶登錄時,密碼匹配階段並沒有進行密碼解密(因為密碼經過Hash處理,是不可逆的),而是使用相同的算法把用戶輸入的密碼進行hash處理,得到密碼的hash值,然后將其與從數據庫中查詢到的密碼hash值進行比較。如果兩者相同,說明用戶輸入的密碼正確。

這正是為什么處理密碼時要用hash算法,而不用加密算法。因為這樣處理即使數據庫泄漏,黑客也很難破解密碼(破解密碼只能用彩虹表)。

3、在spring中使用BcryptPasswordEncoder

1)、引入依賴

<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>

2)、使用

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class EncryptTest {
    @Test
    public void test() {
        String pass = "123456";
        BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
        String hashPass = bcryptPasswordEncoder.encode(pass);
        System.out.println(hashPass);

        boolean f = bcryptPasswordEncoder.matches("123456",hashPass);
        System.out.println(f);

    }
}

結果:

$2a$10$h8.leCDNRc37ESzTtCXz/usAotyF2oyGOVP5qjcBgzckk4DAf3hfW
true

再次執行,結果如下:

$2a$10$UCNZDxvYpN5RuGWRFaPopOlnoItwimY/o/96ePnIR01UVs2o8bPCa
true

可見,每次加密后的結果不一樣,但是每次都能匹配成功。

4、在springboot中使用BcryptPasswordEncoder

如果只是想使用SpringSecurity + SpringBoot完成密碼加密/解密操作,而不使用SpringSecurty提供的其它權證驗證功能。具體步驟如下:

 1)、引入依賴

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2)、添加配置類

我們在添加了spring security依賴后,所有的地址都被spring security所控制了,我們目前只是需要用到BCrypt密碼加密的部分,所以我們要添加一個配置類,配置為所有地址都可以匿名訪問

/**
 * 安全配置類
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable();
    }
}

3)、在啟動類中配置Bean

@MapperScan("com.zwh.dao")
@SpringBootApplication
public class MySpringBootApplication extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(this.getClass());
    }

   @Bean public BCryptPasswordEncoder bcryptPasswordEncoder(){
        return new BCryptPasswordEncoder(); }
}

4)、使用BCryptPasswordEncoder中的方法完成加密/解密

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class EncryptTest {
    @Test
    public void test() {
        String pass = "123456";
        BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
        String hashPass = bcryptPasswordEncoder.encode(pass);
        System.out.println(hashPass);

        boolean f = bcryptPasswordEncoder.matches("123456",hashPass);
        System.out.println(f);

    }
}

結果如下:

$2a$10$S/XfI2AfoDROIZcspSFF7umk/tQsMYVX95PyIzxLrGlrozXuRQ7Ka
true

二、MD5加密

1、第一個工具類

1)、MD5加密工具類

不加鹽MD5加密工具類。

實際開發中,采用不加鹽工具類進行兩次加密存入數據庫,再對登錄時獲取的一次加密的密碼再次加密后,與數據庫兩次加密的進行比較,相等的話則表示密碼相同。

public class MD5Tools {
    
    /**
     * 描述:MD5加密方法
     * @param    text:    待加密的文本
     * @return    mdrStr:    MD5加密后的文本
     * @throws NoSuchAlgorithmException 
     * **/
    public static String EncoderByMd5(String text) throws NoSuchAlgorithmException{
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            BASE64Encoder base64en = new BASE64Encoder();
            return base64en.encode(md5.digest(text.getBytes("UTF-8")));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * MD5加密
     * @param str
     * @return
     */
    public static String getMD5(String str) {
        MessageDigest messageDigest = null;

        try {
            messageDigest = MessageDigest.getInstance("MD5"); // 采用MD5算法

            messageDigest.reset(); // 通過restet初始化

            messageDigest.update(str.getBytes("UTF-8")); // 加密
        } catch (NoSuchAlgorithmException e) {
            System.out.println("NoSuchAlgorithmException caught!");
            System.exit(-1);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        byte[] byteArray = messageDigest.digest(); // 獲取摘要文件

        StringBuffer md5StrBuff = new StringBuffer(); // 存放加密后的字符串

        for (int i = 0; i < byteArray.length; i++) {
            if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)
                md5StrBuff.append("0").append(
                        Integer.toHexString(0xFF & byteArray[i]));
            else
                md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
        }

        return md5StrBuff.toString();

    }
    
    public static String getMD5(String str,String charset) {
        MessageDigest messageDigest = null;

        try {
            messageDigest = MessageDigest.getInstance("MD5");

            messageDigest.reset();

            messageDigest.update(str.getBytes(charset));
        } catch (NoSuchAlgorithmException e) {
            System.out.println("NoSuchAlgorithmException caught!");
            System.exit(-1);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        byte[] byteArray = messageDigest.digest();

        StringBuffer md5StrBuff = new StringBuffer();

        for (int i = 0; i < byteArray.length; i++) {
            if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)
                md5StrBuff.append("0").append(
                        Integer.toHexString(0xFF & byteArray[i]));
            else
                md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
        }

        return md5StrBuff.toString();

    }
    
}

2)、使用

// 驗證用戶名密碼
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        wrapper.eq("user_phone", username).or().eq("user_no", username);
        wrapper.eq("user_pwd", MD5Tools.EncoderByMd5(password));
        User user = userService.getOne(wrapper);

2、第二個工具類

加隨機鹽MD5工具類

public class MD5Utils {

    public static Random random = new SecureRandom();

    /**
     * 加鹽MD5算法
     * @param password
     * @return
     */
    public static String getSaltMD5(String password) throws Exception{
        StringBuilder sb = new StringBuilder(16);
        sb.append(random.nextInt(99999999)).append(random.nextInt(99999999));
        int len = sb.length();
        if(len < 18){
            int diffLen = 16 - len;
            for(int i = 0; i < diffLen; i ++){
                sb.append(0);
            }
        }
        String salt = sb.toString();
        password = md5Hex(password + salt);
        char[] cs = new char[48];
        for (int i = 0; i < 48; i += 3) {
            cs[i] = password.charAt(i / 3 * 2);
            char c = salt.charAt(i / 3);
            cs[i + 1] = c;
            cs[i + 2] = password.charAt(i / 3 * 2 + 1);
        }
        return String.valueOf(cs);
    }

    /**
     * 使用Apache的Hex類實現Hex(16進制字符串和)和字節數組的互轉
     * @param str 原明文字符串
     * @return 轉換后的字符
     */
    private static String md5Hex(String str) throws Exception{
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] digest = md.digest(str.getBytes());
        return new String(new Hex().encode(digest));
    }

    /**
     * 驗證加鹽后是否和原文一致
     * @param password 原始密碼
     * @param md5str md5后值
     * @return
     */
    public static boolean getSaltverifyMD5(String password, String md5str) throws Exception{
        char[] cs1 = new char[32];
        char[] cs2 = new char[16];
        for (int i = 0; i < 48; i += 3) {
            cs1[i / 3 * 2] = md5str.charAt(i);
            cs1[i / 3 * 2 + 1] = md5str.charAt(i + 2);
            cs2[i / 3] = md5str.charAt(i + 1);
        }
        String salt = new String(cs2);
        return md5Hex(password + salt).equals(String.valueOf(cs1));
    }

    public static void main(String[] args) throws Exception {
        System.out.println(getSaltverifyMD5("111111","52da3708888616899226ff6f51c85992b39bd2532639cb79"));
    }
}

1)、加密

jkUser.setPassword(MD5Utils.getSaltMD5(randomPassword));

2)、驗證加鹽后是否和原文是否一致

if (!CollectionUtils.isEmpty(users)) {
                User user = users.get(0);
                String password = user.getPassword();
                boolean saltverifyMD5 = MD5Utils.getSaltverifyMD5(oldPassword, password);
                if (!saltverifyMD5) {
                    rs.setSuccess(false);
                    rs.setMsg("輸入的舊密碼錯誤,請重新輸入!");
                    return rs;
                }
            }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM