跳至主要內容

PasswordEncoder 入门

Jin大约 5 分钟

PasswordEncoder 入门

1、PasswordEncoder

![image-20241122172636855](images/15-PasswordEncoder 入门/image-20241122172636855.png)

2、BCryptPasswordEncoder

BCryptPasswordEncoder 使用的是基于 bcrypt 算法的加密方式,它是一种适用于密码存储的哈希算法,能够有效防止彩虹表攻击(rainbow table attack)和暴力破解攻击。

通过合理设置工作因子,可以在保证加密强度的同时,优化系统的性能。

2.1、BCrypt 算法简介

BCrypt 是一种基于 Blowfish 加密算法 的密钥派生函数(key derivation function),设计上具有抗时间延迟攻击(例如,暴力破解)。BCrypt 是为密码存储和验证而设计的,其主要特点包括:

  • 盐值(Salt):每次对密码加密时都会生成一个随机的盐值。盐值与密码一起进行加密,确保即使用户密码相同,存储在数据库中的哈希值也不相同。
  • 工作因子(Work Factor):工作因子控制哈希计算的迭代次数,增加工作因子可以提高破解的难度(需要更多的计算资源)。BCrypt 默认的工作因子是 10。

2.2、BCryptPasswordEncoder 源码

public class BCryptPasswordEncoder implements PasswordEncoder {

	private Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");

	private final int strength;

	private final BCryptVersion version;

	private final SecureRandom random;
	...
}

构造方法

    public BCryptPasswordEncoder() {
		this(-1);
	}
	...
	public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random) {
		if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
			throw new IllegalArgumentException("Bad strength");
		}
		this.version = version;
		this.strength = (strength == -1) ? 10 : strength;
		this.random = random;
	}

BCrypt 部分源码

	static final int MIN_LOG_ROUNDS = 4;
	static final int MAX_LOG_ROUNDS = 31;

从构造方法可以看出:

  • 默认的工作因子是:10
  • 最大工作因子是:31
  • 最小默认因子是:4

源码路径

org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder

2.3、常用方法

encode

	@Override
	public String encode(CharSequence rawPassword) {
		if (rawPassword == null) {
			throw new IllegalArgumentException("rawPassword cannot be null");
		}
		String salt = getSalt();
		return BCrypt.hashpw(rawPassword.toString(), salt);
	}

这个方法用于对明文密码进行加密,并返回加密后的密码字符串。加密后的密码将包括盐值,形成一个可以直接存储在数据库中的字符串。

示例如下:

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encodedPassword = encoder.encode("Jin");

matches

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

该方法用于验证用户输入的密码是否与存储的加密密码匹配。它会自动提取加密字符串中的盐值,使用相同的加密算法重新加密输入的密码并进行比较。

示例如下:

boolean isMatch = encoder.matches("Jin", encodedPassword);

2.4、工作因子(Strength)

工作因子(Strength)控制 BCrypt 算法的计算强度。它影响加密速度,并且直接影响计算的安全性。工作因子值通常是一个整数,表示对密码进行的迭代次数的对数(2 的幂次方)

  • Strength = 10:默认值,意味着进行 2^10(1024)次加密运算。
  • Strength = 12:意味着进行 2^12(4096)次计算,增加了加密的强度。
  • Strength = 14:意味着进行 2^14(16384)次计算,安全性更高,但会使加密和验证过程更加耗时。

一般来说,选择一个平衡的值(例如,10 到 12 之间)通常足够,但为了提升安全性,可以适当提高工作因子。

2.5、加密过程的细节

当调用 BCryptPasswordEncoder.encode() 方法时,它会执行以下步骤:

  1. 生成一个随机盐值(Salt)。

  2. 使用盐值和明文密码进行多次加密运算。

  3. 返回一个包含盐值和加密密码的字符串,格式通常是:

    $2a$10$hq/W3SU5CvqiuZqYM5WiD.1eLNv8MURsySuTSCyqJIikH1CxnXMNa
    
  4. 其中:

    • $2a$:表示使用的是 bcrypt 算法。
    • 10:表示工作因子(Strength)。
    • 后续的字符串是盐值和加密后的密码。

2.6、验证过程的细节

当调用 BCryptPasswordEncoder.matches() 方法时,它会执行以下步骤:

  1. 从存储的加密密码中提取盐值。
  2. 使用该盐值对输入的明文密码进行加密。
  3. 比较生成的哈希值与存储的哈希值是否相同。

由于 BCrypt 算法在加密时使用了盐值,确保每个用户的密码即使相同,其哈希值也不一样。因此,即使密码相同,存储在数据库中的加密结果也是不同的。

2.7、BCryptPasswordEncoderSpring Security 配合

在 Spring Security 中,通常将 BCryptPasswordEncoderAuthenticationManager 配合使用,以便在用户登录时验证密码。

例如,在使用 InMemoryAuthentication 时,你可以这样配置 BCryptPasswordEncoder

3、Argon2PasswordEncoder

使用Argon2算法对密码进行哈希处理。Argon2是密码哈希比赛的获胜者。为了防止在自定义硬件上进行密码破解,Argon2是一种故意缓慢的算法,需要大量内存。与其他自适应单向函数一样,它应该在您的系统上调整为大约1秒来验证一个密码。当前的Argon2PasswordEncoder实现需要使用BouncyCastle库。

Pbkdf2PasswordEncoder

使用PBKDF2算法对密码进行哈希处理。为了防止密码破解,PBKDF2是一种故意缓慢的算法。与其他自适应单向函数一样,它应该在您的系统上调整为大约1秒来验证一个密码。当需要FIPS认证时,这种算法是一个很好的选择。

SCryptPasswordEncoder

使用scrypt算法对密码进行哈希处理。为了防止在自定义硬件上进行密码破解,scrypt是一种故意缓慢的算法,需要大量内存。与其他自适应单向函数一样,它应该在您的系统上调整为大约1秒来验证一个密码。

DelegatingPasswordEncoder

  • 表中存储的密码形式:{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW
  • 通过如下源码可以知道:可以通过{bcrypt}前缀动态获取和密码的形式类型一致的PasswordEncoder对象
  • 目的:方便随时做密码策略的升级,兼容数据库中的老版本密码策略生成的密码
贡献者: Jin