mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-01 10:59:16 +00:00
Allow upgrading between different BCrypt encodings
Fixes gh-7042
This commit is contained in:
parent
4b0fb19fff
commit
d3d6a8743e
@ -20,6 +20,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@ -32,7 +33,7 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class BCryptPasswordEncoder implements PasswordEncoder {
|
||||
private Pattern BCRYPT_PATTERN = Pattern
|
||||
.compile("\\A\\$2(a|y|b)?\\$\\d\\d\\$[./0-9A-Za-z]{53}");
|
||||
.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final int strength;
|
||||
@ -93,20 +94,16 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
|
||||
throw new IllegalArgumentException("Bad strength");
|
||||
}
|
||||
this.version = version;
|
||||
this.strength = strength;
|
||||
this.strength = strength == -1 ? 10 : strength;
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
public String encode(CharSequence rawPassword) {
|
||||
String salt;
|
||||
if (strength > 0) {
|
||||
if (random != null) {
|
||||
salt = BCrypt.gensalt(version.getVersion(), strength, random);
|
||||
} else {
|
||||
salt = BCrypt.gensalt(version.getVersion(), strength);
|
||||
}
|
||||
if (random != null) {
|
||||
salt = BCrypt.gensalt(version.getVersion(), strength, random);
|
||||
} else {
|
||||
salt = BCrypt.gensalt(version.getVersion());
|
||||
salt = BCrypt.gensalt(version.getVersion(), strength);
|
||||
}
|
||||
return BCrypt.hashpw(rawPassword.toString(), salt);
|
||||
}
|
||||
@ -125,6 +122,23 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
|
||||
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean upgradeEncoding(String encodedPassword) {
|
||||
if (encodedPassword == null || encodedPassword.length() == 0) {
|
||||
logger.warn("Empty encoded password");
|
||||
return false;
|
||||
}
|
||||
|
||||
Matcher matcher = BCRYPT_PATTERN.matcher(encodedPassword);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException("Encoded password does not look like BCrypt: " + encodedPassword);
|
||||
}
|
||||
else {
|
||||
int strength = Integer.parseInt(matcher.group(2));
|
||||
return strength < this.strength;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the default bcrypt version for use in configuration.
|
||||
*
|
||||
|
@ -217,9 +217,15 @@ public class DelegatingPasswordEncoder implements PasswordEncoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean upgradeEncoding(String encodedPassword) {
|
||||
String id = extractId(encodedPassword);
|
||||
return !this.idForEncode.equalsIgnoreCase(id);
|
||||
public boolean upgradeEncoding(String prefixEncodedPassword) {
|
||||
String id = extractId(prefixEncodedPassword);
|
||||
if (!this.idForEncode.equalsIgnoreCase(id)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
|
||||
return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword);
|
||||
}
|
||||
}
|
||||
|
||||
private String extractEncodedPassword(String prefixEncodedPassword) {
|
||||
|
@ -169,4 +169,35 @@ public class BCryptPasswordEncoderTests {
|
||||
assertThat(encoder.matches("password", "012345678901234567890123456789")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void upgradeFromLowerStrength() {
|
||||
BCryptPasswordEncoder weakEncoder = new BCryptPasswordEncoder(5);
|
||||
BCryptPasswordEncoder strongEncoder = new BCryptPasswordEncoder(15);
|
||||
|
||||
String weakPassword = weakEncoder.encode("password");
|
||||
String strongPassword = strongEncoder.encode("password");
|
||||
|
||||
assertThat(weakEncoder.upgradeEncoding(strongPassword)).isFalse();
|
||||
assertThat(strongEncoder.upgradeEncoding(weakPassword)).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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</>
|
||||
*/
|
||||
@Test
|
||||
public void upgradeFromNullOrEmpty() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
assertThat(encoder.upgradeEncoding(null)).isFalse();
|
||||
assertThat(encoder.upgradeEncoding("")).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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</>
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void upgradeFromNonBCrypt() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
encoder.upgradeEncoding("not-a-bcrypt-password");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -215,12 +215,16 @@ public class DelegatingPasswordEncoderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void upgradeEncodingWhenSameIdThenFalse() {
|
||||
assertThat(this.passwordEncoder.upgradeEncoding(this.bcryptEncodedPassword)).isFalse();
|
||||
public void upgradeEncodingWhenSameIdThenEncoderDecides() {
|
||||
this.passwordEncoder.upgradeEncoding(this.bcryptEncodedPassword);
|
||||
|
||||
verify(bcrypt).upgradeEncoding(this.encodedPassword);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void upgradeEncodingWhenDifferentIdThenTrue() {
|
||||
assertThat(this.passwordEncoder.upgradeEncoding(this.noopEncodedPassword)).isTrue();
|
||||
|
||||
verifyZeroInteractions(bcrypt);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user