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));
}
✅ 关键点解析:
- 创建
Argon2PasswordEncoder
实例时配置了五个参数:- 盐值长度:16字节
- 哈希长度:32字节(默认64字节)
- 并行度:1线程
- 内存成本:60000KB
- 迭代次数:10次
- 使用
encode()
方法生成哈希值 - 通过
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仓库获取。