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 441b4cd

Browse files
Create a specific implementation for BalloonHashing and PBKDF2 password encoders using Password4j library
Closes gh-17706
1 parent 564f630 commit 441b4cd

File tree

5 files changed

+693
-3
lines changed

5 files changed

+693
-3
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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 java.security.SecureRandom;
20+
import java.util.Base64;
21+
22+
import com.password4j.AlgorithmFinder;
23+
import com.password4j.BalloonHashingFunction;
24+
import com.password4j.Hash;
25+
import com.password4j.Password;
26+
27+
import org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder;
28+
import org.springframework.util.Assert;
29+
30+
/**
31+
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder}
32+
* that uses the Password4j library with Balloon hashing algorithm.
33+
*
34+
* <p>
35+
* Balloon hashing is a memory-hard password hashing algorithm designed to be resistant to
36+
* both time-memory trade-off attacks and side-channel attacks. This implementation
37+
* handles the salt management explicitly since Password4j's Balloon hashing
38+
* implementation does not include the salt in the output hash.
39+
* </p>
40+
*
41+
* <p>
42+
* The encoded password format is: {salt}:{hash} where both salt and hash are Base64
43+
* encoded.
44+
* </p>
45+
*
46+
* <p>
47+
* This implementation is thread-safe and can be shared across multiple threads.
48+
* </p>
49+
*
50+
* <p>
51+
* <strong>Usage Examples:</strong>
52+
* </p>
53+
* <pre>{@code
54+
* // Using default Balloon hashing settings (recommended)
55+
* PasswordEncoder encoder = new BalloonHashingPassword4jPasswordEncoder();
56+
*
57+
* // Using custom Balloon hashing function
58+
* PasswordEncoder customEncoder = new BalloonHashingPassword4jPasswordEncoder(
59+
* BalloonHashingFunction.getInstance(1024, 3, 4, "SHA-256"));
60+
* }</pre>
61+
*
62+
* @author Mehrdad Bozorgmehr
63+
* @since 7.0
64+
* @see BalloonHashingFunction
65+
* @see AlgorithmFinder#getBalloonHashingInstance()
66+
*/
67+
public class BalloonHashingPassword4jPasswordEncoder extends AbstractValidatingPasswordEncoder {
68+
69+
private static final String DELIMITER = ":";
70+
71+
private static final int DEFAULT_SALT_LENGTH = 32;
72+
73+
private final BalloonHashingFunction balloonHashingFunction;
74+
75+
private final SecureRandom secureRandom;
76+
77+
private final int saltLength;
78+
79+
/**
80+
* Constructs a Balloon hashing password encoder using the default Balloon hashing
81+
* configuration from Password4j's AlgorithmFinder.
82+
*/
83+
public BalloonHashingPassword4jPasswordEncoder() {
84+
this(AlgorithmFinder.getBalloonHashingInstance());
85+
}
86+
87+
/**
88+
* Constructs a Balloon hashing password encoder with a custom Balloon hashing
89+
* function.
90+
* @param balloonHashingFunction the Balloon hashing function to use for encoding
91+
* passwords, must not be null
92+
* @throws IllegalArgumentException if balloonHashingFunction is null
93+
*/
94+
public BalloonHashingPassword4jPasswordEncoder(BalloonHashingFunction balloonHashingFunction) {
95+
this(balloonHashingFunction, DEFAULT_SALT_LENGTH);
96+
}
97+
98+
/**
99+
* Constructs a Balloon hashing password encoder with a custom Balloon hashing
100+
* function and salt length.
101+
* @param balloonHashingFunction the Balloon hashing function to use for encoding
102+
* passwords, must not be null
103+
* @param saltLength the length of the salt in bytes, must be positive
104+
* @throws IllegalArgumentException if balloonHashingFunction is null or saltLength is
105+
* not positive
106+
*/
107+
public BalloonHashingPassword4jPasswordEncoder(BalloonHashingFunction balloonHashingFunction, int saltLength) {
108+
Assert.notNull(balloonHashingFunction, "balloonHashingFunction cannot be null");
109+
Assert.isTrue(saltLength > 0, "saltLength must be positive");
110+
this.balloonHashingFunction = balloonHashingFunction;
111+
this.saltLength = saltLength;
112+
this.secureRandom = new SecureRandom();
113+
}
114+
115+
@Override
116+
protected String encodeNonNullPassword(String rawPassword) {
117+
byte[] salt = new byte[this.saltLength];
118+
this.secureRandom.nextBytes(salt);
119+
120+
Hash hash = Password.hash(rawPassword).addSalt(salt).with(this.balloonHashingFunction);
121+
String encodedSalt = Base64.getEncoder().encodeToString(salt);
122+
String encodedHash = hash.getResult();
123+
124+
return encodedSalt + DELIMITER + encodedHash;
125+
}
126+
127+
@Override
128+
protected boolean matchesNonNull(String rawPassword, String encodedPassword) {
129+
if (!encodedPassword.contains(DELIMITER)) {
130+
return false;
131+
}
132+
133+
String[] parts = encodedPassword.split(DELIMITER, 2);
134+
if (parts.length != 2) {
135+
return false;
136+
}
137+
138+
try {
139+
byte[] salt = Base64.getDecoder().decode(parts[0]);
140+
String expectedHash = parts[1];
141+
142+
Hash hash = Password.hash(rawPassword).addSalt(salt).with(this.balloonHashingFunction);
143+
return expectedHash.equals(hash.getResult());
144+
}
145+
catch (IllegalArgumentException ex) {
146+
// Invalid Base64 encoding
147+
return false;
148+
}
149+
}
150+
151+
@Override
152+
protected boolean upgradeEncodingNonNull(String encodedPassword) {
153+
// For now, we'll return false to maintain existing behavior
154+
// This could be enhanced in the future to check if the encoding parameters
155+
// match the current configuration
156+
return false;
157+
}
158+
159+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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 java.security.SecureRandom;
20+
import java.util.Base64;
21+
22+
import com.password4j.AlgorithmFinder;
23+
import com.password4j.Hash;
24+
import com.password4j.PBKDF2Function;
25+
import com.password4j.Password;
26+
27+
import org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder;
28+
import org.springframework.util.Assert;
29+
30+
/**
31+
* Implementation of {@link org.springframework.security.crypto.password.PasswordEncoder}
32+
* that uses the Password4j library with PBKDF2 hashing algorithm.
33+
*
34+
* <p>
35+
* PBKDF2 is a key derivation function designed to be computationally expensive to thwart
36+
* dictionary and brute force attacks. This implementation handles the salt management
37+
* explicitly since Password4j's PBKDF2 implementation does not include the salt in the
38+
* output hash.
39+
* </p>
40+
*
41+
* <p>
42+
* The encoded password format is: {salt}:{hash} where both salt and hash are Base64
43+
* encoded.
44+
* </p>
45+
*
46+
* <p>
47+
* This implementation is thread-safe and can be shared across multiple threads.
48+
* </p>
49+
*
50+
* <p>
51+
* <strong>Usage Examples:</strong>
52+
* </p>
53+
* <pre>{@code
54+
* // Using default PBKDF2 settings (recommended)
55+
* PasswordEncoder encoder = new Pbkdf2Password4jPasswordEncoder();
56+
*
57+
* // Using custom PBKDF2 function
58+
* PasswordEncoder customEncoder = new Pbkdf2Password4jPasswordEncoder(
59+
* PBKDF2Function.getInstance(Algorithm.HMAC_SHA256, 100000, 256));
60+
* }</pre>
61+
*
62+
* @author Mehrdad Bozorgmehr
63+
* @since 7.0
64+
* @see PBKDF2Function
65+
* @see AlgorithmFinder#getPBKDF2Instance()
66+
*/
67+
public class Pbkdf2Password4jPasswordEncoder extends AbstractValidatingPasswordEncoder {
68+
69+
private static final String DELIMITER = ":";
70+
71+
private static final int DEFAULT_SALT_LENGTH = 32;
72+
73+
private final PBKDF2Function pbkdf2Function;
74+
75+
private final SecureRandom secureRandom;
76+
77+
private final int saltLength;
78+
79+
/**
80+
* Constructs a PBKDF2 password encoder using the default PBKDF2 configuration from
81+
* Password4j's AlgorithmFinder.
82+
*/
83+
public Pbkdf2Password4jPasswordEncoder() {
84+
this(AlgorithmFinder.getPBKDF2Instance());
85+
}
86+
87+
/**
88+
* Constructs a PBKDF2 password encoder with a custom PBKDF2 function.
89+
* @param pbkdf2Function the PBKDF2 function to use for encoding passwords, must not
90+
* be null
91+
* @throws IllegalArgumentException if pbkdf2Function is null
92+
*/
93+
public Pbkdf2Password4jPasswordEncoder(PBKDF2Function pbkdf2Function) {
94+
this(pbkdf2Function, DEFAULT_SALT_LENGTH);
95+
}
96+
97+
/**
98+
* Constructs a PBKDF2 password encoder with a custom PBKDF2 function and salt length.
99+
* @param pbkdf2Function the PBKDF2 function to use for encoding passwords, must not
100+
* be null
101+
* @param saltLength the length of the salt in bytes, must be positive
102+
* @throws IllegalArgumentException if pbkdf2Function is null or saltLength is not
103+
* positive
104+
*/
105+
public Pbkdf2Password4jPasswordEncoder(PBKDF2Function pbkdf2Function, int saltLength) {
106+
Assert.notNull(pbkdf2Function, "pbkdf2Function cannot be null");
107+
Assert.isTrue(saltLength > 0, "saltLength must be positive");
108+
this.pbkdf2Function = pbkdf2Function;
109+
this.saltLength = saltLength;
110+
this.secureRandom = new SecureRandom();
111+
}
112+
113+
@Override
114+
protected String encodeNonNullPassword(String rawPassword) {
115+
byte[] salt = new byte[this.saltLength];
116+
this.secureRandom.nextBytes(salt);
117+
118+
Hash hash = Password.hash(rawPassword).addSalt(salt).with(this.pbkdf2Function);
119+
String encodedSalt = Base64.getEncoder().encodeToString(salt);
120+
String encodedHash = hash.getResult();
121+
122+
return encodedSalt + DELIMITER + encodedHash;
123+
}
124+
125+
@Override
126+
protected boolean matchesNonNull(String rawPassword, String encodedPassword) {
127+
if (!encodedPassword.contains(DELIMITER)) {
128+
return false;
129+
}
130+
131+
String[] parts = encodedPassword.split(DELIMITER, 2);
132+
if (parts.length != 2) {
133+
return false;
134+
}
135+
136+
try {
137+
byte[] salt = Base64.getDecoder().decode(parts[0]);
138+
String expectedHash = parts[1];
139+
140+
Hash hash = Password.hash(rawPassword).addSalt(salt).with(this.pbkdf2Function);
141+
return expectedHash.equals(hash.getResult());
142+
}
143+
catch (IllegalArgumentException ex) {
144+
// Invalid Base64 encoding
145+
return false;
146+
}
147+
}
148+
149+
@Override
150+
protected boolean upgradeEncodingNonNull(String encodedPassword) {
151+
// For now, we'll return false to maintain existing behavior
152+
// This could be enhanced in the future to check if the encoding parameters
153+
// match the current configuration
154+
return false;
155+
}
156+
157+
}

0 commit comments

Comments
(0)

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