mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-01 10:59:16 +00:00
Add BCrypt Revision Support
Fixes: gh-3320
This commit is contained in:
parent
605469db06
commit
388a7b62b9
File diff suppressed because it is too large
Load Diff
@ -24,22 +24,23 @@ import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Implementation of PasswordEncoder that uses the BCrypt strong hashing function. Clients
|
||||
* can optionally supply a "strength" (a.k.a. log rounds in BCrypt) and a SecureRandom
|
||||
* instance. The larger the strength parameter the more work will have to be done
|
||||
* can optionally supply a "version" ($2a, $2b, $2y) and a "strength" (a.k.a. log rounds in BCrypt)
|
||||
* and a SecureRandom instance. The larger the strength parameter the more work will have to be done
|
||||
* (exponentially) to hash the passwords. The default value is 10.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class BCryptPasswordEncoder implements PasswordEncoder {
|
||||
private Pattern BCRYPT_PATTERN = Pattern
|
||||
.compile("\\A\\$2a?\\$\\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;
|
||||
private final BCryptVersion version;
|
||||
|
||||
private final SecureRandom random;
|
||||
|
||||
|
||||
public BCryptPasswordEncoder() {
|
||||
this(-1);
|
||||
}
|
||||
@ -51,15 +52,47 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
|
||||
this(strength, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param version the version of bcrypt, can be 2a,2b,2y
|
||||
*/
|
||||
public BCryptPasswordEncoder(BCryptVersion version) {
|
||||
this(version, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param version the version of bcrypt, can be 2a,2b,2y
|
||||
* @param random the secure random instance to use
|
||||
*/
|
||||
public BCryptPasswordEncoder(BCryptVersion version, SecureRandom random) {
|
||||
this(version, -1, random);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strength the log rounds to use, between 4 and 31
|
||||
* @param random the secure random instance to use
|
||||
*
|
||||
* @param random the secure random instance to use
|
||||
*/
|
||||
public BCryptPasswordEncoder(int strength, SecureRandom random) {
|
||||
this(BCryptVersion.$2A, strength, random);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param version the version of bcrypt, can be 2a,2b,2y
|
||||
* @param strength the log rounds to use, between 4 and 31
|
||||
*/
|
||||
public BCryptPasswordEncoder(BCryptVersion version, int strength) {
|
||||
this(version, strength, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param version the version of bcrypt, can be 2a,2b,2y
|
||||
* @param strength the log rounds to use, between 4 and 31
|
||||
* @param random the secure random instance to use
|
||||
*/
|
||||
public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random) {
|
||||
if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
|
||||
throw new IllegalArgumentException("Bad strength");
|
||||
}
|
||||
this.version = version;
|
||||
this.strength = strength;
|
||||
this.random = random;
|
||||
}
|
||||
@ -68,14 +101,12 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
|
||||
String salt;
|
||||
if (strength > 0) {
|
||||
if (random != null) {
|
||||
salt = BCrypt.gensalt(strength, random);
|
||||
salt = BCrypt.gensalt(version.getVersion(), strength, random);
|
||||
} else {
|
||||
salt = BCrypt.gensalt(version.getVersion(), strength);
|
||||
}
|
||||
else {
|
||||
salt = BCrypt.gensalt(strength);
|
||||
}
|
||||
}
|
||||
else {
|
||||
salt = BCrypt.gensalt();
|
||||
} else {
|
||||
salt = BCrypt.gensalt(version.getVersion());
|
||||
}
|
||||
return BCrypt.hashpw(rawPassword.toString(), salt);
|
||||
}
|
||||
@ -93,4 +124,25 @@ public class BCryptPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the default bcrypt version for use in configuration.
|
||||
*
|
||||
* @author Lin Feng
|
||||
*/
|
||||
public enum BCryptVersion {
|
||||
$2A("$2a"),
|
||||
$2Y("$2y"),
|
||||
$2B("$2b");
|
||||
|
||||
private final String version;
|
||||
|
||||
BCryptVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class BCryptPasswordEncoderTests {
|
||||
|
||||
@Test
|
||||
public void matches() {
|
||||
public void $2yMatches() {
|
||||
// $2y is default version
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
String result = encoder.encode("password");
|
||||
assertThat(result.equals("password")).isFalse();
|
||||
@ -36,7 +37,24 @@ public class BCryptPasswordEncoderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unicode() {
|
||||
public void $2aMatches() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2A);
|
||||
String result = encoder.encode("password");
|
||||
assertThat(result.equals("password")).isFalse();
|
||||
assertThat(encoder.matches("password", result)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void $2bMatches() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B);
|
||||
String result = encoder.encode("password");
|
||||
assertThat(result.equals("password")).isFalse();
|
||||
assertThat(encoder.matches("password", result)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void $2yUnicode() {
|
||||
// $2y is default version
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
String result = encoder.encode("passw\u9292rd");
|
||||
assertThat(encoder.matches("pass\u9292\u9292rd", result)).isFalse();
|
||||
@ -44,17 +62,68 @@ public class BCryptPasswordEncoderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notMatches() {
|
||||
public void $2aUnicode() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2A);
|
||||
String result = encoder.encode("passw\u9292rd");
|
||||
assertThat(encoder.matches("pass\u9292\u9292rd", result)).isFalse();
|
||||
assertThat(encoder.matches("passw\u9292rd", result)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void $2bUnicode() {
|
||||
BCryptPasswordEncoder encoder =
|
||||
new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B);
|
||||
String result = encoder.encode("passw\u9292rd");
|
||||
assertThat(encoder.matches("pass\u9292\u9292rd", result)).isFalse();
|
||||
assertThat(encoder.matches("passw\u9292rd", result)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void $2yNotMatches() {
|
||||
// $2y is default version
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
String result = encoder.encode("password");
|
||||
assertThat(encoder.matches("bogus", result)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customStrength() {
|
||||
public void $2aNotMatches() {
|
||||
BCryptPasswordEncoder encoder =
|
||||
new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2A);
|
||||
String result = encoder.encode("password");
|
||||
assertThat(encoder.matches("bogus", result)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void $2bNotMatches() {
|
||||
BCryptPasswordEncoder encoder =
|
||||
new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B);
|
||||
String result = encoder.encode("password");
|
||||
assertThat(encoder.matches("bogus", result)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void $2yCustomStrength() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(8);
|
||||
String result = encoder.encode("password");
|
||||
assertThat(encoder.matches("password", result)).isTrue();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void $2aCustomStrength() {
|
||||
BCryptPasswordEncoder encoder =
|
||||
new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2A, 8);
|
||||
String result = encoder.encode("password");
|
||||
assertThat(encoder.matches("password", result)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void $2bCustomStrength() {
|
||||
BCryptPasswordEncoder encoder =
|
||||
new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B, 8);
|
||||
String result = encoder.encode("password");
|
||||
assertThat(encoder.matches("password", result)).isTrue();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
|
@ -43,6 +43,14 @@ public class BCryptTests {
|
||||
"$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" },
|
||||
{ "", "$2a$12$k42ZFHFWqBp3vWli.nIn8u",
|
||||
"$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" },
|
||||
{ "", "$2b$06$8eVN9RiU8Yki430X.wBvN.",
|
||||
"$2b$06$8eVN9RiU8Yki430X.wBvN.LWaqh2962emLVSVXVZIXJvDYLsV0oFu" },
|
||||
{ "", "$2b$06$NlgfNgpIc6GlHciCkMEW8u",
|
||||
"$2b$06$NlgfNgpIc6GlHciCkMEW8uKOBsyvAp7QwlHpysOlKdtyEw50WQua2" },
|
||||
{ "", "$2y$06$mFDtkz6UN7B3GZ2qi2hhaO",
|
||||
"$2y$06$mFDtkz6UN7B3GZ2qi2hhaO3OFWzNEdcY84ELw6iHCPruuQfSAXBLK" },
|
||||
{ "", "$2y$06$88kSqVttBx.e9iXTPCLa5u",
|
||||
"$2y$06$88kSqVttBx.e9iXTPCLa5uFPrVFjfLH4D.KcO6pBiAmvUkvdg0EYy" },
|
||||
{ "a", "$2a$06$m0CrhHm10qJ3lXRY.5zDGO",
|
||||
"$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" },
|
||||
{ "a", "$2a$08$cfcvVd2aQ8CMvoMpP2EBfe",
|
||||
@ -51,6 +59,14 @@ public class BCryptTests {
|
||||
"$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" },
|
||||
{ "a", "$2a$12$8NJH3LsPrANStV6XtBakCe",
|
||||
"$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" },
|
||||
{ "a", "$2b$06$ehKGYiS4wt2HAr7KQXS5z.",
|
||||
"$2b$06$ehKGYiS4wt2HAr7KQXS5z.OaRjB4jHO7rBHJKlGXbqEH3QVJfO7iO" },
|
||||
{ "a", "$2b$06$PWxFFHA3HiCD46TNOZh30e",
|
||||
"$2b$06$PWxFFHA3HiCD46TNOZh30eNto1hg5uM9tHBlI4q/b03SW/gGKUYk6" },
|
||||
{ "a", "$2y$06$LUdD6/aD0e/UbnxVAVbvGu",
|
||||
"$2y$06$LUdD6/aD0e/UbnxVAVbvGuUmIoJ3l/OK94ThhadpMWwKC34LrGEey" },
|
||||
{ "a", "$2y$06$eqgY.T2yloESMZxgp76deO",
|
||||
"$2y$06$eqgY.T2yloESMZxgp76deOROa7nzXDxbO0k.PJvuClTa.Vu1AuemG" },
|
||||
{ "abc", "$2a$06$If6bvum7DFjUnE9p2uDeDu",
|
||||
"$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" },
|
||||
{ "abc", "$2a$08$Ro0CUfOqk6cXEKf3dyaM7O",
|
||||
@ -59,6 +75,14 @@ public class BCryptTests {
|
||||
"$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" },
|
||||
{ "abc", "$2a$12$EXRkfkdmXn2gzds2SSitu.",
|
||||
"$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" },
|
||||
{ "abc", "$2b$06$5FyQoicpbox1xSHFfhhdXu",
|
||||
"$2b$06$5FyQoicpbox1xSHFfhhdXuR2oxLpO1rYsQh5RTkI/9.RIjtoF0/ta" },
|
||||
{ "abc", "$2b$06$1kJyuho8MCVP3HHsjnRMkO",
|
||||
"$2b$06$1kJyuho8MCVP3HHsjnRMkO1nvCOaKTqLnjG2TX1lyMFbXH/aOkgc." },
|
||||
{ "abc", "$2y$06$ACfku9dT6.H8VjdKb8nhlu",
|
||||
"$2y$06$ACfku9dT6.H8VjdKb8nhluaoBmhJyK7GfoNScEfOfrJffUxoUeCjK" },
|
||||
{ "abc", "$2y$06$9JujYcoWPmifvFA3RUP90e",
|
||||
"$2y$06$9JujYcoWPmifvFA3RUP90e5rSEHAb5Ye6iv3.G9ikiHNv5cxjNEse" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz", "$2a$06$.rCVZVOThsIa97pEDOxvGu",
|
||||
"$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz", "$2a$08$aTsUwsyowQuzRrDqFflhge",
|
||||
@ -67,6 +91,14 @@ public class BCryptTests {
|
||||
"$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz", "$2a$12$D4G5f18o7aMMfwasBL7Gpu",
|
||||
"$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz", "$2b$06$O8E89AQPj1zJQA05YvIAU.",
|
||||
"$2b$06$O8E89AQPj1zJQA05YvIAU.hMpj25BXri1bupl/Q7CJMlpLwZDNBoO" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz", "$2b$06$PDqIWr./o/P3EE/P.Q0A/u",
|
||||
"$2b$06$PDqIWr./o/P3EE/P.Q0A/uFg86WL/PXTbaW267TDALEwDylqk00Z." },
|
||||
{ "abcdefghijklmnopqrstuvwxyz", "$2y$06$34MG90ZLah8/ZNr3ltlHCu",
|
||||
"$2y$06$34MG90ZLah8/ZNr3ltlHCuz6bachF8/3S5jTuzF1h2qg2cUk11sFW" },
|
||||
{ "abcdefghijklmnopqrstuvwxyz", "$2y$06$AK.hSLfMyw706iEW24i68u",
|
||||
"$2y$06$AK.hSLfMyw706iEW24i68uKAc2yorPTrB0cimvjJHEBUrPkOq7VvG" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$06$fPIsBO8qRqkjj273rfaOI.",
|
||||
"$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$08$Eq2r4G/76Wv39MzSX262hu",
|
||||
@ -74,7 +106,15 @@ public class BCryptTests {
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe",
|
||||
"$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$12$WApznUOJfkEGSmYRfnkrPO",
|
||||
"$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" } };
|
||||
"$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2b$06$FGWA8OlY6RtQhXBXuCJ8Wu",
|
||||
"$2b$06$FGWA8OlY6RtQhXBXuCJ8WusVipRI15cWOgJK8MYpBHEkktMfbHRIG" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2b$06$G6aYU7UhUEUDJBdTgq3CRe",
|
||||
"$2b$06$G6aYU7UhUEUDJBdTgq3CRekiopCN4O4sNitFXrf5NUscsVZj3a2r6" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2y$06$sYDFHqOcXTjBgOsqC0WCKe",
|
||||
"$2y$06$sYDFHqOcXTjBgOsqC0WCKeMd3T1UhHuWQSxncLGtXDLMrcE6vFDti" },
|
||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2y$06$6Xm0gCw4g7ZNDCEp4yTise",
|
||||
"$2y$06$6Xm0gCw4g7ZNDCEp4yTisez0kSdpXEl66MvdxGidnmChIe8dFmMnq" } };
|
||||
|
||||
/**
|
||||
* Test method for 'BCrypt.hashpw(String, String)'
|
||||
@ -152,7 +192,7 @@ public class BCryptTests {
|
||||
public void testCheckpw_failure() {
|
||||
print("BCrypt.checkpw w/ bad passwords: ");
|
||||
for (int i = 0; i < test_vectors.length; i++) {
|
||||
int broken_index = (i + 4) % test_vectors.length;
|
||||
int broken_index = (i + 8) % test_vectors.length;
|
||||
String plain = test_vectors[i][0];
|
||||
String expected = test_vectors[broken_index][2];
|
||||
assertThat(BCrypt.checkpw(plain, expected)).isFalse();
|
||||
|
Loading…
x
Reference in New Issue
Block a user