Skip to content

Commit fe8626b

Browse files
Add Argon2 and BCrypt and Scrypt password encoders using Password4j library
Closes gh-17706
1 parent 265e1d1 commit fe8626b

9 files changed

+994
-204
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.crypto.password4j;
18+
19+
import com.password4j.AlgorithmFinder;
20+
import com.password4j.Argon2Function;
21+
22+
/**
23+
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder}
24+
* that uses the Password4j library with Argon2 hashing algorithm.
25+
*
26+
* <p>
27+
* Argon2 is the winner of the Password Hashing Competition (2015) and is recommended for
28+
* new applications. It provides excellent resistance against GPU-based attacks and
29+
* includes built-in salt generation. This implementation leverages Password4j's Argon2
30+
* support which properly includes the salt in the output hash.
31+
* </p>
32+
*
33+
* <p>
34+
* This implementation is thread-safe and can be shared across multiple threads.
35+
* </p>
36+
*
37+
* <p>
38+
* <strong>Usage Examples:</strong>
39+
* </p>
40+
* <pre>{@code
41+
* // Using default Argon2 settings (recommended)
42+
* PasswordEncoder encoder = new Argon2Password4jPasswordEncoder();
43+
*
44+
* // Using custom Argon2 configuration
45+
* PasswordEncoder customEncoder = new Argon2Password4jPasswordEncoder(
46+
* Argon2Function.getInstance(65536, 3, 4, 32, Argon2.ID));
47+
* }</pre>
48+
*
49+
* @author Mehrdad Bozorgmehr
50+
* @since 7.0
51+
* @see Argon2Function
52+
* @see AlgorithmFinder#getArgon2Instance()
53+
*/
54+
public class Argon2Password4jPasswordEncoder extends Password4jPasswordEncoder {
55+
56+
/**
57+
* Constructs an Argon2 password encoder using the default Argon2 configuration from
58+
* Password4j's AlgorithmFinder.
59+
*/
60+
public Argon2Password4jPasswordEncoder() {
61+
super(AlgorithmFinder.getArgon2Instance());
62+
}
63+
64+
/**
65+
* Constructs an Argon2 password encoder with a custom Argon2 function.
66+
* @param argon2Function the Argon2 function to use for encoding passwords, must not
67+
* be null
68+
* @throws IllegalArgumentException if argon2Function is null
69+
*/
70+
public Argon2Password4jPasswordEncoder(Argon2Function argon2Function) {
71+
super(argon2Function);
72+
}
73+
74+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.crypto.password4j;
18+
19+
import com.password4j.AlgorithmFinder;
20+
import com.password4j.BcryptFunction;
21+
22+
/**
23+
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder}
24+
* that uses the Password4j library with BCrypt hashing algorithm.
25+
*
26+
* <p>
27+
* BCrypt is a well-established password hashing algorithm that includes built-in salt
28+
* generation and is resistant to rainbow table attacks. This implementation leverages
29+
* Password4j's BCrypt support which properly includes the salt in the output hash.
30+
* </p>
31+
*
32+
* <p>
33+
* This implementation is thread-safe and can be shared across multiple threads.
34+
* </p>
35+
*
36+
* <p>
37+
* <strong>Usage Examples:</strong>
38+
* </p>
39+
* <pre>{@code
40+
* // Using default BCrypt settings (recommended)
41+
* PasswordEncoder encoder = new BcryptPassword4jPasswordEncoder();
42+
*
43+
* // Using custom round count
44+
* PasswordEncoder customEncoder = new BcryptPassword4jPasswordEncoder(BcryptFunction.getInstance(12));
45+
* }</pre>
46+
*
47+
* @author Mehrdad Bozorgmehr
48+
* @since 7.0
49+
* @see BcryptFunction
50+
* @see AlgorithmFinder#getBcryptInstance()
51+
*/
52+
public class BcryptPassword4jPasswordEncoder extends Password4jPasswordEncoder {
53+
54+
/**
55+
* Constructs a BCrypt password encoder using the default BCrypt configuration from
56+
* Password4j's AlgorithmFinder.
57+
*/
58+
public BcryptPassword4jPasswordEncoder() {
59+
super(AlgorithmFinder.getBcryptInstance());
60+
}
61+
62+
/**
63+
* Constructs a BCrypt password encoder with a custom BCrypt function.
64+
* @param bcryptFunction the BCrypt function to use for encoding passwords, must not
65+
* be null
66+
* @throws IllegalArgumentException if bcryptFunction is null
67+
*/
68+
public BcryptPassword4jPasswordEncoder(BcryptFunction bcryptFunction) {
69+
super(bcryptFunction);
70+
}
71+
72+
}

crypto/src/main/java/org/springframework/security/crypto/password4j/Password4jPasswordEncoder.java

Lines changed: 13 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -16,116 +16,56 @@
1616

1717
package org.springframework.security.crypto.password4j;
1818

19-
import com.password4j.AlgorithmFinder;
2019
import com.password4j.Hash;
2120
import com.password4j.HashingFunction;
2221
import com.password4j.Password;
23-
import org.apache.commons.logging.Log;
24-
import org.apache.commons.logging.LogFactory;
2522

2623
import org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder;
2724
import org.springframework.util.Assert;
2825

2926
/**
30-
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder}
31-
* that uses the Password4j library. This encoder supports multiple password hashing
32-
* algorithms including BCrypt, SCrypt, Argon2, and PBKDF2.
27+
* Abstract base class for Password4j-based password encoders. This class provides the
28+
* common functionality for password encoding and verification using the Password4j
29+
* library.
3330
*
3431
* <p>
35-
* The encoder uses the provided {@link HashingFunction} for both encoding and
36-
* verification. Password4j can automatically detect the algorithm used in existing hashes
37-
* during verification.
32+
* This class is package-private and should not be used directly. Instead, use the
33+
* specific public subclasses that support verified hashing algorithms such as BCrypt,
34+
* Argon2, and SCrypt implementations.
3835
* </p>
3936
*
4037
* <p>
4138
* This implementation is thread-safe and can be shared across multiple threads.
4239
* </p>
4340
*
44-
* <p>
45-
* <strong>Usage Examples:</strong>
46-
* </p>
47-
* <pre>{@code
48-
* // Using default algorithms from AlgorithmFinder (recommended approach)
49-
* PasswordEncoder bcryptEncoder = new Password4jPasswordEncoder(AlgorithmFinder.getBcryptInstance());
50-
* PasswordEncoder argon2Encoder = new Password4jPasswordEncoder(AlgorithmFinder.getArgon2Instance());
51-
* PasswordEncoder scryptEncoder = new Password4jPasswordEncoder(AlgorithmFinder.getScryptInstance());
52-
* PasswordEncoder pbkdf2Encoder = new Password4jPasswordEncoder(AlgorithmFinder.getPBKDF2Instance());
53-
*
54-
* // Using customized algorithm parameters
55-
* PasswordEncoder customBcrypt = new Password4jPasswordEncoder(BcryptFunction.getInstance(12));
56-
* PasswordEncoder customArgon2 = new Password4jPasswordEncoder(
57-
* Argon2Function.getInstance(65536, 3, 4, 32, Argon2.ID));
58-
* PasswordEncoder customScrypt = new Password4jPasswordEncoder(
59-
* ScryptFunction.getInstance(32768, 8, 1, 32));
60-
* PasswordEncoder customPbkdf2 = new Password4jPasswordEncoder(
61-
* CompressedPBKDF2Function.getInstance("SHA256", 310000, 32));
62-
* }</pre>
63-
*
6441
* @author Mehrdad Bozorgmehr
6542
* @since 7.0
66-
* @see AlgorithmFinder
6743
*/
68-
public class Password4jPasswordEncoder extends AbstractValidatingPasswordEncoder {
69-
70-
private final Log logger = LogFactory.getLog(getClass());
44+
abstract class Password4jPasswordEncoder extends AbstractValidatingPasswordEncoder {
7145

7246
private final HashingFunction hashingFunction;
7347

7448
/**
75-
* Constructs a Password4j password encoder with the specified hashing function.
76-
*
77-
* <p>
78-
* It is recommended to use password4j's {@link AlgorithmFinder} to obtain default
79-
* instances with secure configurations:
80-
* </p>
81-
* <ul>
82-
* <li>{@code AlgorithmFinder.getBcryptInstance()} - BCrypt with default settings</li>
83-
* <li>{@code AlgorithmFinder.getArgon2Instance()} - Argon2 with default settings</li>
84-
* <li>{@code AlgorithmFinder.getScryptInstance()} - SCrypt with default settings</li>
85-
* <li>{@code AlgorithmFinder.getPBKDF2Instance()} - PBKDF2 with default settings</li>
86-
* </ul>
87-
*
88-
* <p>
89-
* For custom configurations, you can create specific function instances:
90-
* </p>
91-
* <ul>
92-
* <li>{@code BcryptFunction.getInstance(12)} - BCrypt with 12 rounds</li>
93-
* <li>{@code Argon2Function.getInstance(65536, 3, 4, 32, Argon2.ID)} - Custom
94-
* Argon2</li>
95-
* <li>{@code ScryptFunction.getInstance(16384, 8, 1, 32)} - Custom SCrypt</li>
96-
* <li>{@code CompressedPBKDF2Function.getInstance("SHA256", 310000, 32)} - Custom
97-
* PBKDF2</li>
98-
* </ul>
49+
* Constructs a Password4j password encoder with the specified hashing function. This
50+
* constructor is package-private and intended for use by subclasses only.
9951
* @param hashingFunction the hashing function to use for encoding passwords, must not
10052
* be null
10153
* @throws IllegalArgumentException if hashingFunction is null
10254
*/
103-
public Password4jPasswordEncoder(HashingFunction hashingFunction) {
55+
Password4jPasswordEncoder(HashingFunction hashingFunction) {
10456
Assert.notNull(hashingFunction, "hashingFunction cannot be null");
10557
this.hashingFunction = hashingFunction;
10658
}
10759

10860
@Override
10961
protected String encodeNonNullPassword(String rawPassword) {
110-
try {
111-
Hash hash = Password.hash(rawPassword).with(this.hashingFunction);
112-
return hash.getResult();
113-
}
114-
catch (Exception ex) {
115-
throw new IllegalStateException("Failed to encode password using Password4j", ex);
116-
}
62+
Hash hash = Password.hash(rawPassword).with(this.hashingFunction);
63+
return hash.getResult();
11764
}
11865

11966
@Override
12067
protected boolean matchesNonNull(String rawPassword, String encodedPassword) {
121-
try {
122-
// Use the specific hashing function for verification
123-
return Password.check(rawPassword, encodedPassword).with(this.hashingFunction);
124-
}
125-
catch (Exception ex) {
126-
this.logger.warn("Password verification failed for encoded password: " + encodedPassword, ex);
127-
return false;
128-
}
68+
return Password.check(rawPassword, encodedPassword).with(this.hashingFunction);
12969
}
13070

13171
@Override
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.crypto.password4j;
18+
19+
import com.password4j.AlgorithmFinder;
20+
import com.password4j.ScryptFunction;
21+
22+
/**
23+
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder}
24+
* that uses the Password4j library with SCrypt hashing algorithm.
25+
*
26+
* <p>
27+
* SCrypt is a memory-hard password hashing algorithm designed to be resistant to hardware
28+
* brute-force attacks. It includes built-in salt generation and is particularly effective
29+
* against ASIC and GPU-based attacks. This implementation leverages Password4j's SCrypt
30+
* support which properly includes the salt in the output hash.
31+
* </p>
32+
*
33+
* <p>
34+
* This implementation is thread-safe and can be shared across multiple threads.
35+
* </p>
36+
*
37+
* <p>
38+
* <strong>Usage Examples:</strong>
39+
* </p>
40+
* <pre>{@code
41+
* // Using default SCrypt settings (recommended)
42+
* PasswordEncoder encoder = new ScryptPassword4jPasswordEncoder();
43+
*
44+
* // Using custom SCrypt configuration
45+
* PasswordEncoder customEncoder = new ScryptPassword4jPasswordEncoder(
46+
* ScryptFunction.getInstance(32768, 8, 1, 32));
47+
* }</pre>
48+
*
49+
* @author Mehrdad Bozorgmehr
50+
* @since 7.0
51+
* @see ScryptFunction
52+
* @see AlgorithmFinder#getScryptInstance()
53+
*/
54+
public class ScryptPassword4jPasswordEncoder extends Password4jPasswordEncoder {
55+
56+
/**
57+
* Constructs an SCrypt password encoder using the default SCrypt configuration from
58+
* Password4j's AlgorithmFinder.
59+
*/
60+
public ScryptPassword4jPasswordEncoder() {
61+
super(AlgorithmFinder.getScryptInstance());
62+
}
63+
64+
/**
65+
* Constructs an SCrypt password encoder with a custom SCrypt function.
66+
* @param scryptFunction the SCrypt function to use for encoding passwords, must not
67+
* be null
68+
* @throws IllegalArgumentException if scryptFunction is null
69+
*/
70+
public ScryptPassword4jPasswordEncoder(ScryptFunction scryptFunction) {
71+
super(scryptFunction);
72+
}
73+
74+
}

0 commit comments

Comments
 (0)