Skip to content

Commit d3d6a87

Browse files
larsgreferrwinch
authored andcommitted
Allow upgrading between different BCrypt encodings
Fixes gh-7042
1 parent 4b0fb19 commit d3d6a87

File tree

4 files changed

+69
-14
lines changed

4 files changed

+69
-14
lines changed

crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java

+23-9
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.springframework.security.crypto.password.PasswordEncoder;
2121

2222
import java.security.SecureRandom;
23+
import java.util.regex.Matcher;
2324
import java.util.regex.Pattern;
2425

2526
/**
@@ -32,7 +33,7 @@
3233
*/
3334
public class BCryptPasswordEncoder implements PasswordEncoder {
3435
private Pattern BCRYPT_PATTERN = Pattern
35-
.compile("\\A\\$2(a|y|b)?\\$\\d\\d\\$[./0-9A-Za-z]{53}");
36+
.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");
3637
private final Log logger = LogFactory.getLog(getClass());
3738

3839
private final int strength;
@@ -93,20 +94,16 @@ public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom r
9394
throw new IllegalArgumentException("Bad strength");
9495
}
9596
this.version = version;
96-
this.strength = strength;
97+
this.strength = strength == -1 ? 10 : strength;
9798
this.random = random;
9899
}
99100

100101
public String encode(CharSequence rawPassword) {
101102
String salt;
102-
if (strength > 0) {
103-
if (random != null) {
104-
salt = BCrypt.gensalt(version.getVersion(), strength, random);
105-
} else {
106-
salt = BCrypt.gensalt(version.getVersion(), strength);
107-
}
103+
if (random != null) {
104+
salt = BCrypt.gensalt(version.getVersion(), strength, random);
108105
} else {
109-
salt = BCrypt.gensalt(version.getVersion());
106+
salt = BCrypt.gensalt(version.getVersion(), strength);
110107
}
111108
return BCrypt.hashpw(rawPassword.toString(), salt);
112109
}
@@ -125,6 +122,23 @@ public boolean matches(CharSequence rawPassword, String encodedPassword) {
125122
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
126123
}
127124

125+
@Override
126+
public boolean upgradeEncoding(String encodedPassword) {
127+
if (encodedPassword == null || encodedPassword.length() == 0) {
128+
logger.warn("Empty encoded password");
129+
return false;
130+
}
131+
132+
Matcher matcher = BCRYPT_PATTERN.matcher(encodedPassword);
133+
if (!matcher.matches()) {
134+
throw new IllegalArgumentException("Encoded password does not look like BCrypt: " + encodedPassword);
135+
}
136+
else {
137+
int strength = Integer.parseInt(matcher.group(2));
138+
return strength < this.strength;
139+
}
140+
}
141+
128142
/**
129143
* Stores the default bcrypt version for use in configuration.
130144
*

crypto/src/main/java/org/springframework/security/crypto/password/DelegatingPasswordEncoder.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,15 @@ private String extractId(String prefixEncodedPassword) {
217217
}
218218

219219
@Override
220-
public boolean upgradeEncoding(String encodedPassword) {
221-
String id = extractId(encodedPassword);
222-
return !this.idForEncode.equalsIgnoreCase(id);
220+
public boolean upgradeEncoding(String prefixEncodedPassword) {
221+
String id = extractId(prefixEncodedPassword);
222+
if (!this.idForEncode.equalsIgnoreCase(id)) {
223+
return true;
224+
}
225+
else {
226+
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
227+
return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword);
228+
}
223229
}
224230

225231
private String extractEncodedPassword(String prefixEncodedPassword) {

crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java

+31
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,35 @@ public void doesntMatchBogusEncodedValue() {
169169
assertThat(encoder.matches("password", "012345678901234567890123456789")).isFalse();
170170
}
171171

172+
@Test
173+
public void upgradeFromLowerStrength() {
174+
BCryptPasswordEncoder weakEncoder = new BCryptPasswordEncoder(5);
175+
BCryptPasswordEncoder strongEncoder = new BCryptPasswordEncoder(15);
176+
177+
String weakPassword = weakEncoder.encode("password");
178+
String strongPassword = strongEncoder.encode("password");
179+
180+
assertThat(weakEncoder.upgradeEncoding(strongPassword)).isFalse();
181+
assertThat(strongEncoder.upgradeEncoding(weakPassword)).isTrue();
182+
}
183+
184+
/**
185+
* @see <a href="https://github.com/spring-projects/spring-security/pull/7042#issuecomment-506755496">https://github.com/spring-projects/spring-security/pull/7042#issuecomment-506755496</>
186+
*/
187+
@Test
188+
public void upgradeFromNullOrEmpty() {
189+
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
190+
assertThat(encoder.upgradeEncoding(null)).isFalse();
191+
assertThat(encoder.upgradeEncoding("")).isFalse();
192+
}
193+
194+
/**
195+
* @see <a href="https://github.com/spring-projects/spring-security/pull/7042#issuecomment-506755496">https://github.com/spring-projects/spring-security/pull/7042#issuecomment-506755496</>
196+
*/
197+
@Test(expected = IllegalArgumentException.class)
198+
public void upgradeFromNonBCrypt() {
199+
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
200+
encoder.upgradeEncoding("not-a-bcrypt-password");
201+
}
202+
172203
}

crypto/src/test/java/org/springframework/security/crypto/password/DelegatingPasswordEncoderTests.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,16 @@ public void upgradeEncodingWhenIdInvalidFormatThenTrue() {
215215
}
216216

217217
@Test
218-
public void upgradeEncodingWhenSameIdThenFalse() {
219-
assertThat(this.passwordEncoder.upgradeEncoding(this.bcryptEncodedPassword)).isFalse();
218+
public void upgradeEncodingWhenSameIdThenEncoderDecides() {
219+
this.passwordEncoder.upgradeEncoding(this.bcryptEncodedPassword);
220+
221+
verify(bcrypt).upgradeEncoding(this.encodedPassword);
220222
}
221223

222224
@Test
223225
public void upgradeEncodingWhenDifferentIdThenTrue() {
224226
assertThat(this.passwordEncoder.upgradeEncoding(this.noopEncodedPassword)).isTrue();
227+
228+
verifyZeroInteractions(bcrypt);
225229
}
226230
}

0 commit comments

Comments
 (0)