Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 74cf846

Browse files
author
M.Bozorgmehr
committed
Add Password4jPasswordEncoder for enhanced password hashing support
1 parent 49f308a commit 74cf846

File tree

7 files changed

+883
-1
lines changed

7 files changed

+883
-1
lines changed

‎crypto/spring-security-crypto.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ dependencies {
88
management platform(project(":spring-security-dependencies"))
99
optional 'org.springframework:spring-core'
1010
optional 'org.bouncycastle:bcpkix-jdk18on'
11+
optional 'com.password4j:password4j'
1112

1213
testImplementation "org.assertj:assertj-core"
1314
testImplementation "org.junit.jupiter:junit-jupiter-api"

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
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;
2728
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
2829

2930
/**
@@ -65,6 +66,10 @@ private PasswordEncoderFactories() {
6566
* <li>argon2 - {@link Argon2PasswordEncoder#defaultsForSpringSecurity_v5_2()}</li>
6667
* <li>argon2@SpringSecurity_v5_8 -
6768
* {@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>
6873
* </ul>
6974
* @return the {@link PasswordEncoder} to use
7075
*/
@@ -87,6 +92,14 @@ public static PasswordEncoder createDelegatingPasswordEncoder() {
8792
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
8893
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
8994
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+
90103
return new DelegatingPasswordEncoder(encodingId, encoders);
91104
}
92105

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
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.*;
20+
import com.password4j.types.Argon2;
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
import org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder;
24+
import org.springframework.util.Assert;
25+
26+
/**
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.
29+
*
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>
32+
*
33+
* <p>This implementation is thread-safe and can be shared across multiple threads.</p>
34+
*
35+
* @author Mehrdad Bozorgmehr
36+
* @since 6.5
37+
*/
38+
public class Password4jPasswordEncoder extends AbstractValidatingPasswordEncoder {
39+
40+
private final Log logger = LogFactory.getLog(getClass());
41+
42+
private final HashingFunction hashingFunction;
43+
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+
/**
81+
* Constructs a Password4j password encoder with the specified algorithm using default parameters.
82+
*
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.
93+
*
94+
* @param hashingFunction the custom hashing function to use
95+
* @param algorithm the password hashing algorithm type
96+
*/
97+
public Password4jPasswordEncoder(HashingFunction hashingFunction, Password4jAlgorithm algorithm) {
98+
Assert.notNull(hashingFunction, "hashingFunction cannot be null");
99+
Assert.notNull(algorithm, "algorithm cannot be null");
100+
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+
}
185+
186+
@Override
187+
protected String encodeNonNullPassword(String rawPassword) {
188+
try {
189+
Hash hash = Password.hash(rawPassword).with(this.hashingFunction);
190+
return hash.getResult();
191+
} catch (Exception ex) {
192+
throw new IllegalStateException("Failed to encode password using Password4j", ex);
193+
}
194+
}
195+
196+
@Override
197+
protected boolean matchesNonNull(String rawPassword, String encodedPassword) {
198+
try {
199+
// Use the specific hashing function for verification
200+
return Password.check(rawPassword, encodedPassword).with(this.hashingFunction);
201+
} catch (Exception ex) {
202+
this.logger.warn("Password verification failed for encoded password: " + encodedPassword, ex);
203+
return false;
204+
}
205+
}
206+
207+
@Override
208+
protected boolean upgradeEncodingNonNull(String encodedPassword) {
209+
// Password4j handles upgrade detection internally for most algorithms
210+
// For now, we'll return false to maintain existing behavior
211+
return false;
212+
}
213+
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+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
18+
@NullMarked
19+
package org.springframework.security.crypto.password4j;
20+
21+
import org.jspecify.annotations.NullMarked;

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /