1. 概述

在开发涉及用户认证的Web应用时,保护用户免受黑客攻击至关重要。绝大多数Web应用都不会存储明文密码,而是存储密码的哈希值。哈希和加盐是保护密码免受攻击的关键技术。

本文将深入探讨哈希与加盐技术,并演示如何在Java中使用Argon2实现密码哈希。

2. 密码哈希与加盐

密码哈希和加盐是增强数据库密码安全性的两种核心技术。哈希算法通过数学运算将密码转换为一串随机字符

然而,黑客可能通过对比常见密码的哈希值来破解密码。为防止此类攻击,密码加盐技术应运而生。

密码加盐是在应用哈希算法前,向密码附加一段随机数据(称为盐值)。盐值确保哈希结果的唯一性,即使两个用户使用相同密码,其哈希值也完全不同。

此外,哈希算法是单向的,无法像加密算法那样将哈希值还原为明文。这为密码安全提供了额外保障。

3. 什么是Argon2?

Argon2是一种基于密码的密钥派生函数。它是一种可高度配置的安全密码哈希函数。Argon2是一种内存密集型算法,需要大量内存进行计算,在内存受限的硬件上难以实现

它允许应用根据安全需求自定义算法参数,这对不同安全等级的应用至关重要。

由于Argon2提供高级别的安全性,它被推荐用于需要强密码保护的应用。它能有效抵御GPU和其他专用硬件的攻击。

4. 使用Argon2进行哈希

Argon2的核心优势在于其高度可配置性。我们可以设置迭代次数(密码被哈希的次数)。更高的迭代次数会增加哈希耗时,但能显著提升安全性

此外,可以设置内存成本(Argon2使用的内存量)。更高的内存成本会增强安全性,但会消耗更多系统资源

还可以设置并行度(Argon2使用的线程数)。更高的并行度会加快哈希速度,但会降低安全性

接下来我们将通过Spring Security Crypto库和Bouncy Castle库实现Argon2哈希。

4.1. 使用Spring Security Crypto实现Argon2哈希

Spring Security Crypto库提供了Argon2PasswordEncoder类用于密码哈希其内部依赖Bouncy Castle库

首先添加Maven依赖:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-crypto</artifactId>
    <version>6.0.3</version>
</dependency>

下面是一个单元测试示例:

@Test
public void givenRawPassword_whenEncodedWithArgon2_thenMatchesEncodedPassword() {
    String rawPassword = "Baeldung";
    Argon2PasswordEncoder arg2SpringSecurity = new Argon2PasswordEncoder(16, 32, 1, 60000, 10);
    String springBouncyHash = arg2SpringSecurity.encode(rawPassword);
        
    assertTrue(arg2SpringSecurity.matches(rawPassword, springBouncyHash));
}

✅ 关键点解析:

  1. 创建Argon2PasswordEncoder实例时配置了五个参数:
    • 盐值长度:16字节
    • 哈希长度:32字节(默认64字节)
    • 并行度:1线程
    • 内存成本:60000KB
    • 迭代次数:10次
  2. 使用encode()方法生成哈希值
  3. 通过matches()验证密码匹配性

⚠️ 注意:参数配置需在安全性和性能间取得平衡。

4.2. 使用Bouncy Castle实现Argon2哈希

Bouncy Castle库提供更底层的实现方式。首先添加依赖:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk18on</artifactId>
    <version>1.76</version>
</dependency>

实现步骤如下:

生成随机盐值

private byte[] generateSalt16Byte() {
    SecureRandom secureRandom = new SecureRandom();
    byte[] salt = new byte[16];
    secureRandom.nextBytes(salt);
        
    return salt;
}

完整哈希实现

@Test
public void givenRawPasswordAndSalt_whenArgon2AlgorithmIsUsed_thenHashIsCorrect() {
    byte[] salt = generateSalt16Byte();
    String password = "Baeldung";
        
    int iterations = 2;
    int memLimit = 66536;
    int hashLength = 32;
    int parallelism = 1;
        
    Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
      .withVersion(Argon2Parameters.ARGON2_VERSION_13)
      .withIterations(iterations)
      .withMemoryAsKB(memLimit)
      .withParallelism(parallelism)
      .withSalt(salt);
        
    Argon2BytesGenerator generate = new Argon2BytesGenerator();
    generate.init(builder.build());
    byte[] result = new byte[hashLength];
    generate.generateBytes(password.getBytes(StandardCharsets.UTF_8), result, 0, result.length);
        
    Argon2BytesGenerator verifier = new Argon2BytesGenerator();
    verifier.init(builder.build());
    byte[] testHash = new byte[hashLength];
    verifier.generateBytes(password.getBytes(StandardCharsets.UTF_8), testHash, 0, testHash.length);
        
    assertTrue(Arrays.equals(result, testHash));
}

❌ 踩坑提醒:

  • 必须使用相同的参数配置才能正确验证哈希值
  • 盐值需要安全存储(通常与哈希值一起保存)
  • 字符编码需保持一致(示例使用UTF-8)

5. 总结

本文系统介绍了密码哈希与加盐的核心概念,深入剖析了Argon2算法的特性,并通过Spring Security Crypto和Bouncy Castle两种方式实现了密码哈希。Spring Security Crypto封装了底层细节,使用更简单;而Bouncy Castle提供了更灵活的参数控制。

完整示例代码可在GitHub仓库获取。


原始标题:Hashing With Argon2 in Java