Add BCrypt Revision Support

Fixes: gh-3320
This commit is contained in:
linfeng 2018-10-18 15:59:48 +08:00 committed by Rob Winch
parent 605469db06
commit 388a7b62b9
4 changed files with 766 additions and 357 deletions

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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();