算法~PBKDF2-SHA让密码更安全

摘要:在当今的数字世界中,密码安全是至关重要的。为了保护用户密码免受未经授权的访问和破解,Password-Based Key Derivation Function 2 (PBKDF2)算法成为了一种重要的工具。

在 PBKDF2 算法中,SHA 表示 Secure Hash Algorithm,它是一系列密码哈希函数的标准,其中 SHA-1、SHA-256、SHA-384 和 SHA-512 等是常见的版本。因此,PBKDF2-SHA 就是使用 SHA 系列算法作为其内部哈希函数的密码派生函数。

PBKDF2-SHA 算法的工作原理是通过多次迭代将输入的密码和盐值进行混合和计算,以生成一个安全的密钥。这样设计可以增加攻击者破解密码所需的时间和资源成本,提高密码的安全性。

PBKDF2算法的优点

PBKDF2算法具有以下优点:

2.1. 密码安全性提升

PBKDF2算法通过迭代应用一个伪随机函数来增加密码的安全性。这种迭代过程使得破解者需要更多的计算资源和时间来破解密码,从而大大增加了密码的安全性。

2.2. 强大的密钥派生功能

PBKDF2算法可以根据用户提供的密码和盐值生成一个强大的密钥。这个密钥可以用于加密和解密数据,同时也可以用于生成消息验证码等。

2.3. 可扩展性和灵活性

PBKDF2算法可以根据需要进行迭代次数的调整,以适应不同的安全需求。这使得算法具有较高的灵活性,并可以根据应用程序的要求进行调整。

3. PBKDF2算法的缺点

尽管PBKDF2算法具有许多优点,但也存在一些缺点:

3.1. 计算资源消耗较高

由于PBKDF2算法需要进行多次迭代,因此它对计算资源的消耗相对较高。这可能会对一些资源有限的设备或系统造成一定的负担。

3.2. 不适合高速加密需求

由于PBKDF2算法的计算量较大,它在高速加密需求的场景下可能表现不佳。对于这些场景,可以考虑使用更高效的密钥派生函数。

4. PBKDF2算法的应用

PBKDF2算法主要应用于密码存储和验证过程中。它解决了以下问题:

4.1. 密码泄露的风险

通过将用户密码转换为密钥,PBKDF2算法可以大大降低密码泄露的风险。即使攻击者获取了存储的密码数据,他们也无法轻易地破解出原始密码。

4.2. 弱密码的安全性

PBKDF2算法可以增加弱密码的安全性。通过迭代和盐值的引入,即使用户选择了弱密码,破解者也需要付出更大的代价才能破解密码。

在keycloak中的应用

在使用 PBKDF2-SHA256 算法进行密码哈希时,通常会将生成的盐值和哈希后的密码一起存储在数据库中。当用户下次输入相同的明文密码时,您需要按照以下步骤来对比用户输入的密码与库里存储的密码是否相同:

  1. 从数据库中获取该用户的盐值和已经哈希后的密码。
  2. 使用获取到的盐值和用户输入的明文密码,结合 PBKDF2-SHA256 算法再次计算哈希值。
  3. 将上一步得到的哈希值与数据库中存储的哈希密码进行比较。
  4. 如果两个哈希值相同,则说明用户输入的密码是正确的;如果不同,则密码不匹配。

简而言之,您需要在用户登录时重新计算哈希值,并将其与数据库中存储的哈希密码进行比较以验证用户身份。这样设计可以保证用户密码的安全性,同时也能够防止彩虹表攻击等恶意破解手段。

PBKDF2工具类

/**
 * PB KDF2 SHA工具类
 *
 * @author lind
 * @date 2024/5/8 8:20
 * @since 1.0.0
 */
public class PBKDF2SHAUtils {

	private static final String PBKDF_2_WITH_HMAC_SHA_512 = "PBKDF2WithHmacSHA512";

	private static final int ITERATIONS = 30000;

	private static final int DERIVED_KEY_SIZE = 256;

	/**
	 * PB KDF2 SHA256加密
	 * @param rawPassword
	 * @param salt
	 * @return
	 */
	public static String encodedCredential(String rawPassword,byte[] salt){
		return encodedCredential(rawPassword, ITERATIONS,salt, DERIVED_KEY_SIZE);
	}
	// 加密
	public static String encodedCredential(String rawPassword, int iterations, byte[] salt, int derivedKeySize) {
		KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, derivedKeySize);

		try {
			byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();
			return new String(Base64.getEncoder().encode(key));
		}
		catch (InvalidKeySpecException e) {
			throw new RuntimeException("Credential could not be encoded", e);
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	// 随机盐
	public static byte[] getSalt() {
		byte[] buffer = new byte[16];
		SecureRandom secureRandom = new SecureRandom();
		secureRandom.nextBytes(buffer);
		return buffer;
	}

	private static SecretKeyFactory getSecretKeyFactory() {
		try {
			return SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_512);
		}
		catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("PBKDF2 algorithm not found", e);
		}
	}

}

单元测试代码

@Test
public void generatePassword() {
	String rawPassword = "123456";//原密码
	byte[] salt = PBKDF2SHAUtils.getSalt();// 随机盐,需要存储
	String encodePass = PBKDF2SHAUtils.encodedCredential(rawPassword,  salt);// 秘文,需要存储
	System.out.println("encodePass:" + encodePass);
	String formData = "123456"; // 表单数据
	Assert.equals(encodePass, PBKDF2SHAUtils.encodedCredential(formData,  salt));// 与库里密码对比

}