1616
1717package 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 ;
2123import org .apache .commons .logging .Log ;
2224import org .apache .commons .logging .LogFactory ;
25+
2326import org .springframework .security .crypto .password .AbstractValidatingPasswordEncoder ;
2427import 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 */
3868public 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}
0 commit comments