Skip to content

Commit 04f994c

Browse files
Refactor Password4jPasswordEncoder to use AlgorithmFinder for algorithm selection and enhance documentation
Closes gh-17706 Signed-off-by: M.Bozorgmehr <[email protected]>
1 parent b711da9 commit 04f994c

File tree

5 files changed

+177
-755
lines changed

5 files changed

+177
-755
lines changed

crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
2525
import org.springframework.security.crypto.password.PasswordEncoder;
2626
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
27-
import org.springframework.security.crypto.password4j.Password4jPasswordEncoder;
2827
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
2928

3029
/**
@@ -66,10 +65,6 @@ private PasswordEncoderFactories() {
6665
* <li>argon2 - {@link Argon2PasswordEncoder#defaultsForSpringSecurity_v5_2()}</li>
6766
* <li>argon2@SpringSecurity_v5_8 -
6867
* {@link Argon2PasswordEncoder#defaultsForSpringSecurity_v5_8()}</li>
69-
* <li>password4j-bcrypt - {@link Password4jPasswordEncoder} with BCrypt</li>
70-
* <li>password4j-scrypt - {@link Password4jPasswordEncoder} with SCrypt</li>
71-
* <li>password4j-argon2 - {@link Password4jPasswordEncoder} with Argon2</li>
72-
* <li>password4j-pbkdf2 - {@link Password4jPasswordEncoder} with PBKDF2</li>
7368
* </ul>
7469
* @return the {@link PasswordEncoder} to use
7570
*/
@@ -92,14 +87,6 @@ public static PasswordEncoder createDelegatingPasswordEncoder() {
9287
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
9388
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
9489
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
95-
96-
// Password4j implementations
97-
encoders.put("password4j-bcrypt", Password4jPasswordEncoder.bcrypt(10));
98-
encoders.put("password4j-scrypt", Password4jPasswordEncoder.scrypt(16384, 8, 1, 32));
99-
encoders.put("password4j-argon2", Password4jPasswordEncoder.argon2(65536, 3, 4, 32,
100-
com.password4j.types.Argon2.ID));
101-
encoders.put("password4j-pbkdf2", Password4jPasswordEncoder.pbkdf2(310000, 32));
102-
10390
return new DelegatingPasswordEncoder(encodingId, encoders);
10491
}
10592

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

Lines changed: 68 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -16,179 +16,102 @@
1616

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

19-
import com.password4j.*;
20-
import com.password4j.types.Argon2;
19+
import com.password4j.AlgorithmFinder;
20+
import com.password4j.Hash;
21+
import com.password4j.HashingFunction;
22+
import com.password4j.Password;
2123
import org.apache.commons.logging.Log;
2224
import org.apache.commons.logging.LogFactory;
25+
2326
import org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder;
2427
import org.springframework.util.Assert;
2528

2629
/**
27-
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder} that uses the Password4j library.
28-
* This encoder supports multiple password hashing algorithms including BCrypt, SCrypt, Argon2, and PBKDF2.
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.
33+
*
34+
* <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.
38+
* </p>
2939
*
30-
* <p>The encoder determines the algorithm used based on the algorithm type specified during construction.
31-
* For verification, it can automatically detect the algorithm used in existing hashes.</p>
40+
* <p>
41+
* This implementation is thread-safe and can be shared across multiple threads.
42+
* </p>
3243
*
33-
* <p>This implementation is thread-safe and can be shared across multiple threads.</p>
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>
3463
*
3564
* @author Mehrdad Bozorgmehr
36-
* @since 6.5
65+
* @since 7.0
66+
* @see AlgorithmFinder
3767
*/
3868
public class Password4jPasswordEncoder extends AbstractValidatingPasswordEncoder {
3969

4070
private final Log logger = LogFactory.getLog(getClass());
4171

4272
private final HashingFunction hashingFunction;
4373

44-
private final Password4jAlgorithm algorithm;
45-
46-
47-
/**
48-
* Enumeration of supported Password4j algorithms.
49-
*/
50-
public enum Password4jAlgorithm {
51-
/**
52-
* BCrypt algorithm.
53-
*/
54-
BCRYPT,
55-
/**
56-
* SCrypt algorithm.
57-
*/
58-
SCRYPT,
59-
/**
60-
* Argon2 algorithm.
61-
*/
62-
ARGON2,
63-
/**
64-
* PBKDF2 algorithm.
65-
*/
66-
PBKDF2,
67-
/**
68-
* Compressed PBKDF2 algorithm.
69-
*/
70-
COMPRESSED_PBKDF2
71-
}
72-
73-
/**
74-
* Constructs a Password4j password encoder with the default BCrypt algorithm.
75-
*/
76-
public Password4jPasswordEncoder() {
77-
this(Password4jAlgorithm.BCRYPT);
78-
}
79-
8074
/**
81-
* Constructs a Password4j password encoder with the specified algorithm using default parameters.
75+
* Constructs a Password4j password encoder with the specified hashing function.
8276
*
83-
* @param algorithm the password hashing algorithm to use
84-
*/
85-
public Password4jPasswordEncoder(Password4jAlgorithm algorithm) {
86-
Assert.notNull(algorithm, "algorithm cannot be null");
87-
this.algorithm = algorithm;
88-
this.hashingFunction = createDefaultHashingFunction(algorithm);
89-
}
90-
91-
/**
92-
* Constructs a Password4j password encoder with a custom hashing function.
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>
9387
*
94-
* @param hashingFunction the custom hashing function to use
95-
* @param algorithm the password hashing algorithm type
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>
99+
* @param hashingFunction the hashing function to use for encoding passwords, must not
100+
* be null
101+
* @throws IllegalArgumentException if hashingFunction is null
96102
*/
97-
public Password4jPasswordEncoder(HashingFunction hashingFunction, Password4jAlgorithm algorithm) {
103+
public Password4jPasswordEncoder(HashingFunction hashingFunction) {
98104
Assert.notNull(hashingFunction, "hashingFunction cannot be null");
99-
Assert.notNull(algorithm, "algorithm cannot be null");
100105
this.hashingFunction = hashingFunction;
101-
this.algorithm = algorithm;
102-
}
103-
104-
/**
105-
* Creates a Password4j password encoder with BCrypt algorithm and specified rounds.
106-
*
107-
* @param rounds the number of rounds (cost factor) for BCrypt
108-
* @return a new Password4j password encoder
109-
*/
110-
public static Password4jPasswordEncoder bcrypt(int rounds) {
111-
return new Password4jPasswordEncoder(BcryptFunction.getInstance(rounds), Password4jAlgorithm.BCRYPT);
112-
}
113-
114-
/**
115-
* Creates a Password4j password encoder with SCrypt algorithm and specified parameters.
116-
*
117-
* @param workFactor the work factor (N parameter)
118-
* @param resources the resources (r parameter)
119-
* @param parallelization the parallelization (p parameter)
120-
* @param derivedKeyLength the derived key length
121-
* @return a new Password4j password encoder
122-
*/
123-
public static Password4jPasswordEncoder scrypt(int workFactor, int resources, int parallelization, int derivedKeyLength) {
124-
return new Password4jPasswordEncoder(
125-
ScryptFunction.getInstance(workFactor, resources, parallelization, derivedKeyLength),
126-
Password4jAlgorithm.SCRYPT
127-
);
128-
}
129-
130-
/**
131-
* Creates a Password4j password encoder with Argon2 algorithm and specified parameters.
132-
*
133-
* @param memory the memory cost
134-
* @param iterations the number of iterations
135-
* @param parallelism the parallelism
136-
* @param outputLength the output length
137-
* @param type the Argon2 type
138-
* @return a new Password4j password encoder
139-
*/
140-
public static Password4jPasswordEncoder argon2(int memory, int iterations, int parallelism, int outputLength, Argon2 type) {
141-
return new Password4jPasswordEncoder(
142-
Argon2Function.getInstance(memory, iterations, parallelism, outputLength, type),
143-
Password4jAlgorithm.ARGON2
144-
);
145-
}
146-
147-
/**
148-
* Creates a Password4j password encoder with PBKDF2 algorithm and specified parameters.
149-
*
150-
* @param iterations the number of iterations
151-
* @param derivedKeyLength the derived key length
152-
* @return a new Password4j password encoder
153-
*/
154-
public static Password4jPasswordEncoder pbkdf2(int iterations, int derivedKeyLength) {
155-
return new Password4jPasswordEncoder(
156-
CompressedPBKDF2Function.getInstance("SHA256", iterations, derivedKeyLength),
157-
Password4jAlgorithm.PBKDF2
158-
);
159-
}
160-
161-
/**
162-
* Creates a Password4j password encoder with compressed PBKDF2 algorithm.
163-
*
164-
* @param iterations the number of iterations
165-
* @param derivedKeyLength the derived key length
166-
* @return a new Password4j password encoder
167-
*/
168-
public static Password4jPasswordEncoder compressedPbkdf2(int iterations, int derivedKeyLength) {
169-
return new Password4jPasswordEncoder(
170-
CompressedPBKDF2Function.getInstance("SHA256", iterations, derivedKeyLength),
171-
Password4jAlgorithm.COMPRESSED_PBKDF2
172-
);
173-
}
174-
175-
/**
176-
* Creates a Password4j password encoder with default settings for Spring Security v5.8+.
177-
* This uses BCrypt with 10 rounds.
178-
*
179-
* @return a new Password4j password encoder with recommended defaults
180-
* @since 6.5
181-
*/
182-
public static Password4jPasswordEncoder defaultsForSpringSecurity() {
183-
return bcrypt(10);
184106
}
185107

186108
@Override
187109
protected String encodeNonNullPassword(String rawPassword) {
188110
try {
189111
Hash hash = Password.hash(rawPassword).with(this.hashingFunction);
190112
return hash.getResult();
191-
} catch (Exception ex) {
113+
}
114+
catch (Exception ex) {
192115
throw new IllegalStateException("Failed to encode password using Password4j", ex);
193116
}
194117
}
@@ -198,7 +121,8 @@ protected boolean matchesNonNull(String rawPassword, String encodedPassword) {
198121
try {
199122
// Use the specific hashing function for verification
200123
return Password.check(rawPassword, encodedPassword).with(this.hashingFunction);
201-
} catch (Exception ex) {
124+
}
125+
catch (Exception ex) {
202126
this.logger.warn("Password verification failed for encoded password: " + encodedPassword, ex);
203127
return false;
204128
}
@@ -211,39 +135,4 @@ protected boolean upgradeEncodingNonNull(String encodedPassword) {
211135
return false;
212136
}
213137

214-
/**
215-
* Creates a default hashing function for the specified algorithm.
216-
*
217-
* @param algorithm the password hashing algorithm
218-
* @return the default hashing function
219-
*/
220-
private static HashingFunction createDefaultHashingFunction(Password4jAlgorithm algorithm) {
221-
return switch (algorithm) {
222-
case BCRYPT -> BcryptFunction.getInstance(10); // Default 10 rounds
223-
case SCRYPT -> ScryptFunction.getInstance(16384, 8, 1, 32); // Default parameters
224-
case ARGON2 -> Argon2Function.getInstance(65536, 3, 4, 32, Argon2.ID); // Default parameters
225-
case PBKDF2 ->
226-
CompressedPBKDF2Function.getInstance("SHA256", 310000, 32); // Use compressed format for self-contained encoding
227-
case COMPRESSED_PBKDF2 -> CompressedPBKDF2Function.getInstance("SHA256", 310000, 32);
228-
};
229-
}
230-
231-
/**
232-
* Gets the algorithm used by this encoder.
233-
*
234-
* @return the password hashing algorithm
235-
*/
236-
public Password4jAlgorithm getAlgorithm() {
237-
return this.algorithm;
238-
}
239-
240-
/**
241-
* Gets the hashing function used by this encoder.
242-
*
243-
* @return the hashing function
244-
*/
245-
public HashingFunction getHashingFunction() {
246-
return this.hashingFunction;
247-
}
248-
249138
}

crypto/src/main/java/org/springframework/security/crypto/password4j/package-info.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
* limitations under the License.
1515
*/
1616

17-
1817
@NullMarked
1918
package org.springframework.security.crypto.password4j;
2019

0 commit comments

Comments
 (0)