SpringSecurity中BCryptPasswordEncoder加密原理

本文详细探讨了Spring Boot (SB) 中的密码加密机制,重点介绍了其使用哈希算法(如BCrypt)进行密码保护的方式。哈希算法虽然不可逆,但SB通过在加密过程中引入盐值,使得在验证密码时能够匹配原始输入。BCryptPasswordEncoder实现了PasswordEncoder接口,用于密码的加密和匹配。加密过程包括生成随机盐并结合明文密码进行哈希运算,而匹配过程则是重新使用相同算法和盐值加密前端输入的明文,再与存储的密文对比。这种方法确保了即使密码被泄露,也无法直接还原原始密码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SB相关问题

想对SB中的密码进行解密,就去了解了一下怎么解密。

SB中的加密方式,是哈希算法,听说这种算法是不可逆的。

那SB是怎么对密码进行验证的。

百度一下,我就知道

百度出来了两个名词,加密算法和哈希算法。

他们都说加密算法和哈希算法是不同的概念,因为加密算法,可解密,而哈希算法,不可逆。

但是SB就是用哈希算法加密的,所以说,哈希算法是加密算法。没毛病。

actually,加密算法,包罗万象。

加密算法包括哈希算法,对称加密算法,非对称加密算法。

即:

哈希算法是加密算法,但是加密算法不是哈希算法。

严格意义上讲,使用MD5、SHA等算法获取数据摘要值的过程应该称为哈希而不是加密,常用于签名,但是大家习惯用加密来说,也就是加密算法了。

常用的三类算法:

哈希加密:MD5、SHA1、HMAC

对称加密:DES、3DES、AES(加密解密使用同一个密钥)

非对称加密:RSA(私钥加密,公钥解密)

MD5安全性低,说是哈希算法不可逆,但解密也轻轻松松的。

言归正传,搜索了很多SB是怎么解密的,都是纯文字,说的有鼻子有眼的,也不知道是不是在忽悠我,还是直接看源码,比较有安全感。

PasswordEncoder接口

BCryptPasswordEncoder类实现了PasswordEncoder接口,接口里有两个方法,encode用于加密,matches用于匹配验证。

package org.springframework.security.crypto.password;

public interface PasswordEncoder {
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

BCryptPasswordEncoder类实现

endode


// 按照一定规则生成盐,然后执行hashpw(明文,盐)方法。
// 记住这个方法,到匹配校对的时候,还会用到它,第一个参数是前端传入的明文,第二个参数是按照规则生成的盐。
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);
    }



  
// 生成盐
public static String gensalt(int log_rounds, SecureRandom random) {
        if (log_rounds >= 4 && log_rounds <= 31) {
            StringBuilder rs = new StringBuilder();
            byte[] rnd = new byte[16];
            random.nextBytes(rnd);
            rs.append("$2a$");
            if (log_rounds < 10) {
                rs.append("0");
            }
            rs.append(log_rounds);
            rs.append("$");
            encode_base64(rnd, rnd.length, rs);
            return rs.toString();
        } else {
            throw new IllegalArgumentException("Bad number of rounds");
        }
    }
// hashpw(明文,盐)方法中,对salt进行二次加工,生成real_salt,这个是最终的盐,之后生成加密字符串。
public static String hashpw(String password, String salt) throws IllegalArgumentException {
        char minor = 0;
        int off = false;
        StringBuilder rs = new StringBuilder();
        if (salt == null) {
            throw new IllegalArgumentException("salt cannot be null");
        } else {
            int saltLength = salt.length();
            if (saltLength < 28) {
                throw new IllegalArgumentException("Invalid salt");
            } else if (salt.charAt(0) == '$' && salt.charAt(1) == '2') {
                byte off;
                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");
                } else if (salt.charAt(off + 2) > '$') {
                    throw new IllegalArgumentException("Missing salt rounds");
                } else {
                    int rounds = Integer.parseInt(salt.substring(off, off + 2));
                    String real_salt = salt.substring(off + 3, off + 25);

                    byte[] passwordb;
                    try {
                        passwordb = (password + (minor >= 'a' ? "\u0000" : "")).getBytes("UTF-8");
                    } catch (UnsupportedEncodingException var13) {
                        throw new AssertionError("UTF-8 is not supported");
                    }

                    byte[] saltb = decode_base64(real_salt, 16);
                    BCrypt B = new BCrypt();
                    byte[] 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();
                }
            } else {
                throw new IllegalArgumentException("Invalid salt version");
            }
        }
    }

matches

// 匹配时,传入参数:前端传入的明文,数据库中的密文。
// 首先校验密文是否BCrypt加密以及是否为空,之后执行checkpw(明文,密文)方法。
  public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (encodedPassword != null && encodedPassword.length() != 0) {
            if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
                this.logger.warn("Encoded password does not look like BCrypt");
                return false;
            } else {
                return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
            }
        } else {
            this.logger.warn("Empty encoded password");
            return false;
        }
    }
// 执行equalsNoEarlyReturn(数据库中密文,hashpw(前端明文,数据库中密文))方法。
// 上边已经说过,hashpw()方法是最终获取加密字符串的。
// 就很清楚了,SB匹配逻辑,是将前端明文加密(采用同样的算法规则,同样的盐),然后与数据库中密文进行比较验证。
// hashpw(明文,盐),可以看到第二个参数是盐耶,可是匹配的时候,数据库中的密文是作为第二个参数(盐)传进去的。
// 所以可以得到我们库中的加密字符串中是存在盐的,这样匹配验证的时候,从加密字符串(salt)中获取到最终盐(real_salt),然后再对前端传入明文加密验证比对。
    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;
        } else {
            byte ret = 0;

            for(int i = 0; i < caa.length; ++i) {
                ret = (byte)(ret | caa[i] ^ cab[i]);
            }

            return ret == 0;
        }
    }
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值