SpringSecurity登录中的密码加密与验证

PasswordEncoderSpring Security提供的一个接口,称它为密码解析器,这个接口主要是处理密码的。源码如下:

public interface PasswordEncoder {

	/**
	 * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
	 * greater hash combined with an 8-byte or greater randomly generated salt.
	 */
	String encode(CharSequence rawPassword);

	/**
	 * Verify the encoded password obtained from storage matches the submitted raw
	 * password after it too is encoded. Returns true if the passwords match, false if
	 * they do not. The stored password itself is never decoded.
	 *
	 * @param rawPassword the raw password to encode and match
	 * @param encodedPassword the encoded password from storage to compare with
	 * @return true if the raw password, after encoding, matches the encoded password from
	 * storage
	 */
	boolean matches(CharSequence rawPassword, String encodedPassword);

	/**
	 * Returns true if the encoded password should be encoded again for better security,
	 * else false. The default implementation always returns false.
	 * @param encodedPassword the encoded password to check
	 * @return true if the encoded password should be encoded again for better security,
	 * else false.
	 */
	default boolean upgradeEncoding(String encodedPassword) {
		return false;
	}
}

接口提供3个方法,第一个方法是对明文密码进行加密的,返回一个密文。第二个方法是匹配明文密码和密文,返回布尔值。第三个方法是对密文进行二次加密,这个方法是默认的。

PasswordEncoder接口有很多实现类,其中最主要的是官方推荐的BCryptPasswordEncoder类,平时使用的最多的就是这个密码解析器。BCryptPasswordEncoder是对bcrypt强散列方法的具体实现,是基于hash算法的单向加密。可以通过strength来控制强度,默认是10

源码如下:

SpringSecurity登录中的密码加密与验证_第1张图片

encode方法是对明文密码进行加密,原理是使用一个随机生成的salt,用明文密码加上这个salt来一起进行加密,返回密文,由于这个salt每次生成的都不一样,所以即使明文密码一样,最后加密出来的密文是不一样的,这样保证了用户密码的安全。

matchs方法是用来匹配明文密码和密文的,最终结果用布尔值返回。

测试加密和匹配:

@Test
public void test1() {
    String password = "123456";// 密码
    PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    for (int i = 1; i <= 5; i++) {
        // 加密明文密码,返回密文
        String encoder = passwordEncoder.encode(password);
        // 明文和密文进行匹配
        boolean bool = passwordEncoder.matches(password, encoder);
        System.out.println(encoder + ":是否匹配?" + bool);
    }

$2a​$10$qcnrAAaJn0g4rnnBc0nz2emAwLXQPe8QYEVbN/YITEFpUZbCH.Pru:是否匹配?true

$2a​$10$BN.YJOmTuHHhj279Lr1r/ue8G9iaYO62y7cjmeonD3toCit.uNfAG:是否匹配?true

$2a​$10$3niTKuqRNbP/8DDAVCw8f.rOyzurdZ1.W0CQAucn8pCf2sihsUkE.:是否匹配?true

$2a​$10$1fTRlKe3YDgSFnSdqgujw.h32cwjtNubcSgCtdc9mNjfGrEtoeJDi:是否匹配?true

$2a​$10$6AIf3GBhWt9WjXc55mObfuRFhn1eyJKOeOdzprg65fmbsWeUrJGdm:是否匹配?true

可以看到,一样的密码,5次加密后的密文全都不一样,但是全都能匹配上。

这是什么原因呢?继续观察源码:

SpringSecurity登录中的密码加密与验证_第2张图片

在明文密码匹配加密密码时,会调用checkpw函数,再看一下函数源码

SpringSecurity登录中的密码加密与验证_第3张图片

SpringSecurity登录中的密码加密与验证_第4张图片

查看hashpw源码:

/**
	 * Hash a password using the OpenBSD bcrypt scheme
	 * @param passwordb	the password to hash, as a byte array
	 * @param salt	the salt to hash with (perhaps generated
	 * using BCrypt.gensalt)
	 * @return	the hashed password
	 */
	public static String hashpw(byte passwordb[], String salt) {
		BCrypt B;
		String real_salt;
		byte saltb[], hashed[];
		char minor = (char) 0;
		int rounds, off;
		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' && minor != 'x' && minor != 'y' && minor != 'b')
					|| salt.charAt(3) != '$')
				throw new IllegalArgumentException ("Invalid salt revision");
			off = 4;
		}

		// Extract number of rounds
		if (salt.charAt(off + 2) > '$')
			throw new IllegalArgumentException ("Missing salt rounds");

		if (off == 4 && saltLength < 29) {
			throw new IllegalArgumentException("Invalid salt");
		}
		rounds = Integer.parseInt(salt.substring(off, off + 2));
		//上面都是验证salt的有效性,real_salt表示密文里保存着加密的真实salt,截取出来后,将明文密码进行加密返回。之后使用
        // equalsNoEarlyReturn 函数 进行匹配验证
		real_salt = salt.substring(off + 3, off + 25);
		saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);

		if (minor >= 'a') // add null terminator
			passwordb = Arrays.copyOf(passwordb, passwordb.length + 1);

		B = new BCrypt();
		hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0);

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

SpringSecurity登录中的密码加密与验证_第5张图片

再查看一下生成salt的函数源码:

SpringSecurity登录中的密码加密与验证_第6张图片

这说明salt生成后,在区间off + 3, off + 25上也保存着base64加密的salt。

结果:

注册时密码由BCrypt加密保存到数据库,密文密码中某个区间内保存着此次hashsalt,由于salt是随机生成的,就算明文密码相同密文密码也不一致,在登录验证时将明文密码使用数据库中获得的密文密码中解析到的salt进行hash,进而匹配密码是否正确。

你可能感兴趣的:(java,spring,spring,security)