最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

spring security的BCryptPasswordEncoder加密和對密碼驗(yàn)證的原理分析

 更新時間:2024年11月28日 16:39:49   作者:農(nóng)碼天下  
文章介紹了加密算法和hash算法的基本概念,以及BCryptPasswordEncoder加密和解密的原理,加密算法是可逆的,需要加鹽以保證安全性,BCryptPasswordEncoder通過生成鹽值并在加密和解密過程中使用,確保相同的明文每次加密結(jié)果不同,從而提高安全性

一、加密算法和hash算法

很多項目中有些機(jī)密的信息需要進(jìn)行加密來保護(hù)用戶或者公司的信息安全,這時這些信息會采用加密以密文的形式暴露在外面。

加密算法是一種可逆的算法,是通過一定的規(guī)則對明文進(jìn)行各種計算的到的密文從而實(shí)現(xiàn)加密的效果。

  • hash算法是不可逆的,常見的MD5加密采用的就是hash的算法進(jìn)行加密。加密算法是可逆的,所以很多情況下加密規(guī)則是很重要的,一旦暴露就可以根據(jù)規(guī)則進(jìn)行逆推得到明文,所以加密算法通常會加鹽,對稱加密和非對稱加密就是加鹽的一種。
  • hash算法雖然不可逆,但通過大數(shù)據(jù)進(jìn)行匹配很多數(shù)據(jù)可以被找到,所以hash算法也是需要加鹽來保證一定的機(jī)密性。

二、BCryptPasswordEncoder 加密和解密的原理

BCryptPasswordEncoder對同樣的數(shù)據(jù)比如“11111”進(jìn)行加密,每次加密的結(jié)果是不相同的,此時就要思考一個問題,同樣的數(shù)據(jù)每次加密不同,那么它是如何進(jìn)行解密的呢?

下面來分析一下此方式的加密源碼,參考了網(wǎng)上的一些資料:

三、源碼解析

BCryptPasswordEncoder類實(shí)現(xiàn)了PasswordEncoder接口,這個接口中定義了兩個方法

public interface PasswordEncoder {
    String encode(CharSequence rawPassword);
    boolean matches(CharSequence rawPassword, String encodedPassword);
}

其中encode(...)是對字符串進(jìn)行加密的方法,matches使用來校驗(yàn)傳入的明文密碼rawPassword是否和加密密碼encodedPassword相匹配的方法。

即對密碼進(jìn)行加密時調(diào)用encode,登錄認(rèn)證時調(diào)用matches

下面我們來看下BCryptPasswordEncoder類中這兩個方法的具體實(shí)現(xiàn)

1. encode方法

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

可以看到,這個方法中先基于某種規(guī)則得到了一個鹽值,然后在調(diào)用BCrypt.hashpw方法,傳入明文密碼和鹽值salt。所以我們再看下BCrypt.hashpw方法中做了什么

2. BCrypt.hashpw方法

public static String hashpw(String password, String salt) throws IllegalArgumentException {
        BCrypt B;
        String real_salt;
        byte passwordb[], saltb[], hashed[];
        char minor = (char) 0;
        int rounds, off = 0;
        StringBuilder rs = new StringBuilder();
        if (salt == null) {
            throw new IllegalArgumentException("salt cannot be null");
        }
        int saltLength = salt.length();
        if (saltLength < 28) {
            throw new IllegalArgumentException("Invalid salt");
        }
        if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
            throw new IllegalArgumentException("Invalid salt version");
        }
        if (salt.charAt(2) == '$') {
            off = 3;
        }
        else {
            minor = salt.charAt(2);
            if (minor != 'a' || salt.charAt(3) != '$') {
                throw new IllegalArgumentException("Invalid salt revision");
            }
            off = 4;
        }
        if (saltLength - off < 25) {
            throw new IllegalArgumentException("Invalid salt");
        }
        // Extract number of rounds
        if (salt.charAt(off + 2) > '$') {
            throw new IllegalArgumentException("Missing salt rounds");
        }
        rounds = Integer.parseInt(salt.substring(off, off + 2));
        real_salt = salt.substring(off + 3, off + 25);
        try {
            passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
        }
        catch (UnsupportedEncodingException uee) {
            throw new AssertionError("UTF-8 is not supported");
        }
        saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
        B = new BCrypt();
        hashed = B.crypt_raw(passwordb, saltb, rounds);
        rs.append("$2");
        if (minor >= 'a') {
            rs.append(minor);
        }
        rs.append("$");
        if (rounds < 10) {
            rs.append("0");
        }
        rs.append(rounds);
        rs.append("$");
        encode_base64(saltb, saltb.length, rs);
        encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs);
        return rs.toString();
    }

可以看到,這個方法中先根據(jù)傳入的鹽值salt,然后基于某種規(guī)則從salt得到real_salt,后續(xù)的操作都是用這個real_salt來進(jìn)行,最終得到加密字符串。

所以這里有一個重點(diǎn):傳入的鹽值salt并不是最終用來加密的鹽,方法中通過salt得到了real_salt,記住這一點(diǎn),因?yàn)楹筮叺钠ヅ浞椒╩atches中要用到這一點(diǎn)。

3. matches方法

matches方法用來判斷一個明文是否和一個加密字符串對應(yīng)。

public boolean matches(CharSequence rawPassword, String encodedPassword) {
    if (encodedPassword == null || encodedPassword.length() == 0) {
        logger.warn("Empty encoded password");
        return false;
    }
    if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
        logger.warn("Encoded password does not look like BCrypt");
        return false;
    }
    return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}

這個方法中先對密文字符串進(jìn)行了一些校驗(yàn),如果不符合規(guī)則直接返回不匹配,然后調(diào)用校驗(yàn)方法BCrypt.checkpw,第一個參數(shù)是明文,第二個參數(shù)是加密后的字符串。

public static boolean checkpw(String plaintext, String hashed) {
    return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));
}
static boolean equalsNoEarlyReturn(String a, String b) {
    char[] caa = a.toCharArray();
    char[] cab = b.toCharArray();
    if (caa.length != cab.length) {
        return false;
    }
    byte ret = 0;
    for (int i = 0; i < caa.length; i++) {
        ret |= caa[i] ^ cab[i];
    }
    return ret == 0;
}

注意:

equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed))

這里,第一個參數(shù)是加密后的字符串,而第二個參數(shù)是用剛才提過的hashpw方法對明文字符串進(jìn)行加密。

hashpw(plaintext, hashed)

第一個參數(shù)是明文,第二個參數(shù)是加密字符串,但是在這里是作為鹽值salt傳入的,所以就用到了剛才說的 hashpw 內(nèi)部通過傳入的salt得到real_salt,這樣就保證了對現(xiàn)在要校驗(yàn)的明文的加密和得到已有密文的加密用的是同樣的加密策略,算法和鹽值都相同,這樣如果新產(chǎn)生的密文和原來的密文相同,則這兩個密文對應(yīng)的明文字符串就是相等的。

這也說明了加密時使用的鹽值被寫在了最終生成的加密字符串中。

總結(jié)

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • idea中maven使用tomcat7插件運(yùn)行run報錯Could not start Tomcat問題

    idea中maven使用tomcat7插件運(yùn)行run報錯Could not start T

    這篇文章主要介紹了idea中maven使用tomcat7插件運(yùn)行run報錯Could not start Tomcat問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-09-09
  • 詳解JVM系列之對象的鎖狀態(tài)和同步

    詳解JVM系列之對象的鎖狀態(tài)和同步

    鎖和同步是java多線程編程中非常常見的使用場景。為了鎖定多線程共享的對象,Java需要提供一定的機(jī)制來實(shí)現(xiàn)共享對象的鎖定。當(dāng)?shù)诙€線程進(jìn)入同一個區(qū)域的時候,必須等待第一個線程解鎖該對象。JVM是怎么做到的呢?快來一起看看吧。
    2021-06-06
  • Java中的轉(zhuǎn)換流InputStreamReader解讀

    Java中的轉(zhuǎn)換流InputStreamReader解讀

    InputStreamReader是Java.io包中的一個類,用于將字節(jié)輸入流轉(zhuǎn)換為字符輸入流,它繼承自java.io.Reader類,提供了兩種構(gòu)造方法,可以使用默認(rèn)或指定字符集創(chuàng)建實(shí)例,常用方法包括讀取字符、判斷是否準(zhǔn)備好讀取數(shù)據(jù)和關(guān)閉流
    2024-09-09
  • JAVA十大排序算法之歸并排序詳解

    JAVA十大排序算法之歸并排序詳解

    這篇文章主要介紹了java中的歸并排序,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • Mybatis查詢多條記錄并返回List集合的方法

    Mybatis查詢多條記錄并返回List集合的方法

    這篇文章主要介紹了Mybatis查詢多條記錄并返回List集合的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • springboot集成dubbo過程詳解

    springboot集成dubbo過程詳解

    本文詳細(xì)介紹了Spring Boot集成Dubbo的過程,包括多個BeanPostProcessor和初始化器的作用,以及ClassPathBeanDefinitionScanner和DubboClassPathBeanDefinitionScanner的使用方法
    2025-11-11
  • Spring DI依賴注入實(shí)戰(zhàn)教程

    Spring DI依賴注入實(shí)戰(zhàn)教程

    這篇文章主要介紹了SpringDI依賴注入實(shí)戰(zhàn)教程,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2024-12-12
  • SprinBoot整合Quart實(shí)現(xiàn)定時調(diào)度的示例代碼

    SprinBoot整合Quart實(shí)現(xiàn)定時調(diào)度的示例代碼

    這篇文章主要介紹了SprinBoot整合Quart實(shí)現(xiàn)定時調(diào)度的示例代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • Java中map遍歷方式的選擇問題詳解

    Java中map遍歷方式的選擇問題詳解

    這篇文章主要介紹了Java中map遍歷方式的選擇問題的相關(guān)內(nèi)容,小編覺得挺不錯的,在則里分享給大家,需要的朋友可以參考下。
    2017-10-10
  • Java并發(fā)編程中高頻異常的原因,場景與解決方案全解析

    Java并發(fā)編程中高頻異常的原因,場景與解決方案全解析

    在Java并發(fā)編程中,異常處理是保障程序穩(wěn)定性的核心環(huán)節(jié),本文將深度解析4類高頻并發(fā)異常的產(chǎn)生原因、典型場景,并給出可落地的解決方案,幫你徹底避開這些坑
    2026-03-03

最新評論

商洛市| 金寨县| 南丰县| 克拉玛依市| 隆昌县| 甘泉县| 盐源县| 峡江县| 商都县| 宁河县| 马鞍山市| 石林| 濮阳县| 遵义市| 丰镇市| 四平市| 洞口县| 阿坝县| 融水| 竹北市| SHOW| 锡林浩特市| 克什克腾旗| 烟台市| 满城县| 阿拉善左旗| 含山县| 滨州市| 远安县| 西平县| 麻栗坡县| 德昌县| 博兴县| 静海县| 抚宁县| 阳城县| 沐川县| 正宁县| 利津县| 襄城县| 姚安县|