SpringSecurity之加密篇

1. 要点概括

BCryptpassword类是SpringSecurity的加密工具,封装了对密码混淆加密的方法,主要是采用盐(salt)对原始密码进行混淆。
本篇介绍的是利用BCryptpassword随机生成盐(salt),使用该盐值对原始密码进行混淆加密。
这种加密方式有两个特点:(1)将随机生成的盐(salt)存到混淆后的代码中;(2)对于相同的明文每一次加密,加密之后的密文都是不一样的;因为盐值(salt)不同。这样的好处就是更增加了密码的安全性。

SpringSecurity中的BCryptPassword采用Hash处理,其过程是不可逆的。

BCryptPassword的加密过程:
(1)加密(encode):用户注册时,使用SHA256+盐(salt)把用户输入的密码进行hash混淆处理,得到密码的hash值,然后将其存入数据库中。
(2)密码校验(matches):用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法结合盐值salt把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。如果两者相同,说明用户输入的密码正确,这里的盐值(salt)是从数据库中查询到的密码hash值中解析出来的。

2. 核心代码分析
//1. BCryptPasswordEncoder类,生成盐salt并混淆rawpassword的代码;
//涉及到两个问题,生成salt盐gensalt(),混淆密码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);
    }
2.1 生成盐(salt):

:包名:org.springframework.security.crypto.bcrypt;类名:BCrypt

	//生成salt盐gensalt() 
    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");
        }
    }
2.2 混淆方法:

包名:org.springframework.security.crypto.bcrypt;类名:BCrypt

//混淆密码hashpw()
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");
            }
        }
    }
2.3 密码的校验

提供的密码与加密之后的密码的校验,使用是BCryptpassword的matches()接口;

    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;
        }
    }

	/**
	 * 密码比较入口,同样是调用了hashpw()方法: 使用的是hashpw()方法,有两个参数:原始明文与数据库中获取的密文。
	 * hashpw()方法是从密文中解析出藏在其中的盐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;
        }
    }
3. 总结

以上是SpringSecurity的BCryptPassword加密方式,上面是介绍其SHA256+随机salt生成密文的基本点。应该还有其他的一些用法,容当后研究。

4. 其它

Springboot框架整合SpringSecurity组件,使用需要使用该加密方式,有一点需要注意,就是在springSecurity的配置文件中注入PasswordEncode的bean

	//SpringSecurityConfigutarion配置类中加入
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
代码示例:
public static void main(String[] args) {
       String password = "leo_epam";
        System.out.println(password + ": encrypt");
        int i = 0;
        while(i < 10){
            BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            String hashedPassword = passwordEncoder.encode(password);
            System.out.println("encryptPassword:" + hashedPassword);
            System.out.println("match result:" + passwordEncoder.matches(password,hashedPassword));
            i ++;
        }
    }

代码输出:

leo_epam: encrypt
encryptPassword:$2a$10$/WIa4YbBMGQX7dBbYwRKx.AmQ3sJ8Ta5wYHh8a7jPlYDbNgMSD/hC
match result:true
encryptPassword:$2a$10$VryRC1lFaTEqUbOUyvu18ulSSGH5hK7JPzAG17ehtkL8aV6fot1ru
match result:true
encryptPassword:$2a$10$0SVRXbfgsqMvpmNDeeqNl.eFPvQ0ojdazk5.x8YDZxIOOP1D4xXge
match result:true
encryptPassword:$2a$10$yWdI4q6DXDZhknTxiT/9ROdhcAARAEY5q4L2YI5uQ3C52Q7Lt/IAa
match result:true
encryptPassword:$2a$10$8yy0.NdQSD2Cig5yIri2eu1XmDRpZSPjsmxDyRg9CZ5afwp/36H2S
match result:true
encryptPassword:$2a$10$QTJpm3jYUCjAoHPVI/uon.FDusm.9tPSc.mk6m.l/kx8aKbIzov3i
match result:true
encryptPassword:$2a$10$CkoGrZqDE86LM1yiX89cpuNDAMdDqemEKMSS3/jquFsxocizgBbX2
match result:true
encryptPassword:$2a$10$6M0N6cQp6kKeXgRl8ftqyOlikAV9YwfMS93xlqTXbd/tmDjFDv3iG
match result:true
encryptPassword:$2a$10$LboMUNAF7vOmucxJI3G/w.cliDRxH1exOmsfy2IlPCUpZU8N7XoO.
match result:true
encryptPassword:$2a$10$ezB0okY.JHv1gt4Y3chiu.5e5R9.vXqd7kNaYH14Vigm9wZ02D8Pe
match result:true
  1. 以上代码就是BCryptPassword的代码简单效果示例,相同的明文,每次生成的密文都是不同的,但是和明文做密码校验都是通过的。
  2. 还有一个就是密码字段的长度,如果打算采用bcrypt加密存储,字段长度不得低于60.
参考引用:
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值