16
16
17
17
package org .springframework .security .crypto .password4j ;
18
18
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 ;
21
23
import org .apache .commons .logging .Log ;
22
24
import org .apache .commons .logging .LogFactory ;
25
+
23
26
import org .springframework .security .crypto .password .AbstractValidatingPasswordEncoder ;
24
27
import org .springframework .util .Assert ;
25
28
26
29
/**
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>
29
39
*
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>
32
43
*
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>
34
63
*
35
64
* @author Mehrdad Bozorgmehr
36
- * @since 6.5
65
+ * @since 7.0
66
+ * @see AlgorithmFinder
37
67
*/
38
68
public class Password4jPasswordEncoder extends AbstractValidatingPasswordEncoder {
39
69
40
70
private final Log logger = LogFactory .getLog (getClass ());
41
71
42
72
private final HashingFunction hashingFunction ;
43
73
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
-
80
74
/**
81
- * Constructs a Password4j password encoder with the specified algorithm using default parameters .
75
+ * Constructs a Password4j password encoder with the specified hashing function .
82
76
*
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>
93
87
*
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
96
102
*/
97
- public Password4jPasswordEncoder (HashingFunction hashingFunction , Password4jAlgorithm algorithm ) {
103
+ public Password4jPasswordEncoder (HashingFunction hashingFunction ) {
98
104
Assert .notNull (hashingFunction , "hashingFunction cannot be null" );
99
- Assert .notNull (algorithm , "algorithm cannot be null" );
100
105
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 );
184
106
}
185
107
186
108
@ Override
187
109
protected String encodeNonNullPassword (String rawPassword ) {
188
110
try {
189
111
Hash hash = Password .hash (rawPassword ).with (this .hashingFunction );
190
112
return hash .getResult ();
191
- } catch (Exception ex ) {
113
+ }
114
+ catch (Exception ex ) {
192
115
throw new IllegalStateException ("Failed to encode password using Password4j" , ex );
193
116
}
194
117
}
@@ -198,7 +121,8 @@ protected boolean matchesNonNull(String rawPassword, String encodedPassword) {
198
121
try {
199
122
// Use the specific hashing function for verification
200
123
return Password .check (rawPassword , encodedPassword ).with (this .hashingFunction );
201
- } catch (Exception ex ) {
124
+ }
125
+ catch (Exception ex ) {
202
126
this .logger .warn ("Password verification failed for encoded password: " + encodedPassword , ex );
203
127
return false ;
204
128
}
@@ -211,39 +135,4 @@ protected boolean upgradeEncodingNonNull(String encodedPassword) {
211
135
return false ;
212
136
}
213
137
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
-
249
138
}
0 commit comments