-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
…] passwords.
- Loading branch information
There are no files selected for viewing
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
|
@@ -55,64 +55,94 @@ public class OpenBSDBCrypt | ||
} | } | ||
} | } | ||
|
|
||
public OpenBSDBCrypt() | private OpenBSDBCrypt() | ||
{ | { | ||
|
|
||
} | } | ||
|
|
||
/** | |||
* Creates a 60 character Bcrypt String, including | |||
* version, cost factor, salt and hash, separated by '$' using version | |||
* '2y'. | |||
* | |||
* @param cost the cost factor, treated as an exponent of 2 | |||
* @param salt a 16 byte salt | |||
* @param password the password | |||
* @return a 60 character Bcrypt String | |||
*/ | |||
public static String generate( | |||
char[] password, | |||
byte[] salt, | |||
int cost) | |||
{ | |||
return generate(defaultVersion, password, salt, cost); | |||
} | |||
|
|||
/** | |||
* Creates a 60 character Bcrypt String, including | |||
* version, cost factor, salt and hash, separated by '$' using version | |||
* '2y'. | |||
* | |||
* @param cost the cost factor, treated as an exponent of 2 | |||
* @param salt a 16 byte salt | |||
* @param password the password | |||
* @return a 60 character Bcrypt String | |||
*/ | |||
public static String generate( | |||
byte[] password, | |||
byte[] salt, | |||
int cost) | |||
{ | |||
return generate(defaultVersion, password, salt, cost); | |||
} | |||
|
|||
/** | /** | ||
* Creates a 60 character Bcrypt String, including | * Creates a 60 character Bcrypt String, including | ||
* version, cost factor, salt and hash, separated by '$' | * version, cost factor, salt and hash, separated by '$' | ||
* | * | ||
* @param version the version, 2y,2b or 2a. (2a is not backwards compatible.) | * @param version the version, may be 2b, 2y or 2a. (2a is not backwards compatible.) | ||
* @param cost the cost factor, treated as an exponent of 2 | * @param cost the cost factor, treated as an exponent of 2 | ||
* @param salt a 16 byte salt | * @param salt a 16 byte salt | ||
* @param password the password | * @param password the password | ||
* @return a 60 character Bcrypt String | * @return a 60 character Bcrypt String | ||
*/ | */ | ||
private static String createBcryptString(String version, | public static String generate( | ||
byte[] password, | String version, | ||
byte[] salt, | char[] password, | ||
int cost) | byte[] salt, | ||
int cost) | |||
{ | { | ||
if (!allowedVersions.contains(version)) | if (password == null) | ||
{ | { | ||
throw new IllegalArgumentException("Version " + version + " is not accepted by this implementation."); | throw new IllegalArgumentException("Password required."); | ||
} | } | ||
|
|
||
StringBuffer sb = new StringBuffer(60); | return doGenerate(version, Strings.toUTF8ByteArray(password), salt, cost); | ||
sb.append('$'); | |||
sb.append(version); | |||
sb.append('$'); | |||
sb.append(cost < 10 ? ("0" + cost) : Integer.toString(cost)); | |||
sb.append('$'); | |||
sb.append(encodeData(salt)); | |||
|
|||
byte[] key = BCrypt.generate(password, salt, cost); | |||
|
|||
sb.append(encodeData(key)); | |||
|
|||
return sb.toString(); | |||
} | } | ||
|
|
||
/** | /** | ||
* Creates a 60 character Bcrypt String, including | * Creates a 60 character Bcrypt String, including | ||
* version, cost factor, salt and hash, separated by '$' using version | * version, cost factor, salt and hash, separated by '$' | ||
* '2y'. | |||
* | * | ||
* @param version the version, may be 2b, 2y or 2a. (2a is not backwards compatible.) | |||
* @param cost the cost factor, treated as an exponent of 2 | * @param cost the cost factor, treated as an exponent of 2 | ||
* @param salt a 16 byte salt | * @param salt a 16 byte salt | ||
* @param password the password | * @param password the password already encoded as a byte array. | ||
* @return a 60 character Bcrypt String | * @return a 60 character Bcrypt String | ||
*/ | */ | ||
public static String generate( | public static String generate( | ||
char[] password, | String version, | ||
byte[] password, | |||
byte[] salt, | byte[] salt, | ||
int cost) | int cost) | ||
{ | { | ||
return generate(defaultVersion, password, salt, cost); | if (password == null) | ||
} | { | ||
throw new IllegalArgumentException("Password required."); | |||
} | |||
|
|
||
return doGenerate(version, Arrays.clone(password), salt, cost); | |||
} | |||
|
|
||
/** | /** | ||
* Creates a 60 character Bcrypt String, including | * Creates a 60 character Bcrypt String, including | ||
|
@@ -121,12 +151,12 @@ public static String generate( | ||
* @param version the version, may be 2b, 2y or 2a. (2a is not backwards compatible.) | * @param version the version, may be 2b, 2y or 2a. (2a is not backwards compatible.) | ||
* @param cost the cost factor, treated as an exponent of 2 | * @param cost the cost factor, treated as an exponent of 2 | ||
* @param salt a 16 byte salt | * @param salt a 16 byte salt | ||
* @param password the password | * @param psw the password | ||
* @return a 60 character Bcrypt String | * @return a 60 character Bcrypt String | ||
*/ | */ | ||
public static String generate( | private static String doGenerate( | ||
String version, | String version, | ||
char[] password, | byte[] psw, | ||
byte[] salt, | byte[] salt, | ||
int cost) | int cost) | ||
{ | { | ||
|
@@ -135,10 +165,6 @@ public static String generate( | ||
throw new IllegalArgumentException("Version " + version + " is not accepted by this implementation."); | throw new IllegalArgumentException("Version " + version + " is not accepted by this implementation."); | ||
} | } | ||
|
|
||
if (password == null) | |||
{ | |||
throw new IllegalArgumentException("Password required."); | |||
} | |||
if (salt == null) | if (salt == null) | ||
{ | { | ||
throw new IllegalArgumentException("Salt required."); | throw new IllegalArgumentException("Salt required."); | ||
|
@@ -152,8 +178,6 @@ else if (salt.length != 16) | ||
throw new IllegalArgumentException("Invalid cost factor."); | throw new IllegalArgumentException("Invalid cost factor."); | ||
} | } | ||
|
|
||
byte[] psw = Strings.toUTF8ByteArray(password); | |||
|
|||
// 0 termination: | // 0 termination: | ||
|
|
||
byte[] tmp = new byte[psw.length >= 72 ? 72 : psw.length + 1]; | byte[] tmp = new byte[psw.length >= 72 ? 72 : psw.length + 1]; | ||
|
@@ -190,11 +214,61 @@ public static boolean checkPassword( | ||
String bcryptString, | String bcryptString, | ||
char[] password) | char[] password) | ||
{ | { | ||
if (password == null) | |||
{ | |||
throw new IllegalArgumentException("Missing password."); | |||
} | |||
|
|||
return doCheckPassword(bcryptString, Strings.toUTF8ByteArray(password)); | |||
} | |||
|
|||
/** | |||
* Checks if a password corresponds to a 60 character Bcrypt String | |||
* | |||
* @param bcryptString a 60 character Bcrypt String, including | |||
* version, cost factor, salt and hash, | |||
* separated by '$' | |||
* @param password the password as an array of bytes | |||
* @return true if the password corresponds to the | |||
* Bcrypt String, otherwise false | |||
*/ | |||
public static boolean checkPassword( | |||
String bcryptString, | |||
byte[] password) | |||
{ | |||
if (password == null) | |||
{ | |||
throw new IllegalArgumentException("Missing password."); | |||
} | |||
|
|||
return doCheckPassword(bcryptString, Arrays.clone(password)); | |||
} | |||
|
|||
/** | |||
* Checks if a password corresponds to a 60 character Bcrypt String | |||
* | |||
* @param bcryptString a 60 character Bcrypt String, including | |||
* version, cost factor, salt and hash, | |||
* separated by '$' | |||
* @param password the password as an array of chars | |||
* @return true if the password corresponds to the | |||
* Bcrypt String, otherwise false | |||
*/ | |||
private static boolean doCheckPassword( | |||
String bcryptString, | |||
byte[] password) | |||
{ | |||
if (bcryptString == null) | |||
{ | |||
throw new IllegalArgumentException("Missing bcryptString."); | |||
} | |||
|
|||
// validate bcryptString: | // validate bcryptString: | ||
if (bcryptString.length() != 60) | final int sLength = bcryptString.length(); | ||
if (sLength != 60) | |||
{ | { | ||
throw new DataLengthException("Bcrypt String length: " | throw new DataLengthException("Bcrypt String length: " | ||
+ bcryptString.length() + ", 60 required."); | + sLength + ", 60 required."); | ||
} | } | ||
|
|
||
if (bcryptString.charAt(0) != '$' | if (bcryptString.charAt(0) != '$' | ||
|
@@ -226,17 +300,53 @@ public static boolean checkPassword( | ||
throw new IllegalArgumentException("Invalid cost factor: " + cost + ", 4 < cost < 31 expected."); | throw new IllegalArgumentException("Invalid cost factor: " + cost + ", 4 < cost < 31 expected."); | ||
} | } | ||
// check password: | // check password: | ||
if (password == null) | |||
{ | |||
throw new IllegalArgumentException("Missing password."); | |||
} | |||
byte[] salt = decodeSaltString( | byte[] salt = decodeSaltString( | ||
bcryptString.substring(bcryptString.lastIndexOf('$') + 1, | bcryptString.substring(bcryptString.lastIndexOf('$') + 1, | ||
bcryptString.length() - 31)); | sLength - 31)); | ||
|
|||
String newBcryptString = doGenerate(version, password, salt, cost); | |||
|
|||
boolean isEqual = sLength == newBcryptString.length(); | |||
for (int i = 0; i != sLength; i++) | |||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
rzwitserloot
|
|||
{ | |||
isEqual &= (bcryptString.indexOf(i) == newBcryptString.indexOf(i)); | |||
} | |||
return isEqual; | |||
} | |||
|
|||
/** | |||
* Creates a 60 character Bcrypt String, including | |||
* version, cost factor, salt and hash, separated by '$' | |||
* | |||
* @param version the version, 2y,2b or 2a. (2a is not backwards compatible.) | |||
* @param cost the cost factor, treated as an exponent of 2 | |||
* @param salt a 16 byte salt | |||
* @param password the password | |||
* @return a 60 character Bcrypt String | |||
*/ | |||
private static String createBcryptString(String version, | |||
byte[] password, | |||
byte[] salt, | |||
int cost) | |||
{ | |||
if (!allowedVersions.contains(version)) | |||
{ | |||
throw new IllegalArgumentException("Version " + version + " is not accepted by this implementation."); | |||
} | |||
|
|||
StringBuilder sb = new StringBuilder(60); | |||
sb.append('$'); | |||
sb.append(version); | |||
sb.append('$'); | |||
sb.append(cost < 10 ? ("0" + cost) : Integer.toString(cost)); | |||
sb.append('$'); | |||
encodeData(sb, salt); | |||
|
|
||
String newBcryptString = generate(version, password, salt, cost); | byte[] key = BCrypt.generate(password, salt, cost); | ||
|
|
||
return bcryptString.equals(newBcryptString); | encodeData(sb, key); | ||
|
|||
return sb.toString(); | |||
} | } | ||
|
|
||
/* | /* | ||
|
@@ -245,9 +355,9 @@ public static boolean checkPassword( | ||
* @param a byte representation of the salt or the password | * @param a byte representation of the salt or the password | ||
* @return the Bcrypt base64 String | * @return the Bcrypt base64 String | ||
*/ | */ | ||
private static String encodeData( | private static void encodeData( | ||
StringBuilder sb, | |||
byte[] data) | byte[] data) | ||
|
|||
{ | { | ||
if (data.length != 24 && data.length != 16) // 192 bit key or 128 bit salt expected | if (data.length != 24 && data.length != 16) // 192 bit key or 128 bit salt expected | ||
{ | { | ||
|
@@ -266,7 +376,6 @@ private static String encodeData( | ||
data[data.length - 1] = (byte)0; | data[data.length - 1] = (byte)0; | ||
} | } | ||
|
|
||
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |||
int len = data.length; | int len = data.length; | ||
|
|
||
int a1, a2, a3; | int a1, a2, a3; | ||
|
@@ -277,24 +386,22 @@ private static String encodeData( | ||
a2 = data[i + 1] & 0xff; | a2 = data[i + 1] & 0xff; | ||
a3 = data[i + 2] & 0xff; | a3 = data[i + 2] & 0xff; | ||
|
|
||
out.write(encodingTable[(a1 >>> 2) & 0x3f]); | sb.append((char)encodingTable[(a1 >>> 2) & 0x3f]); | ||
out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); | sb.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); | ||
out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); | sb.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); | ||
out.write(encodingTable[a3 & 0x3f]); | sb.append((char)encodingTable[a3 & 0x3f]); | ||
} | } | ||
|
|
||
String result = Strings.fromByteArray(out.toByteArray()); | |||
if (salt == true)// truncate padding | if (salt == true)// truncate padding | ||
{ | { | ||
return result.substring(0, 22); | sb.setLength(sb.length() - 2); | ||
} | } | ||
else | else | ||
{ | { | ||
return result.substring(0, result.length() - 1); | sb.setLength(sb.length() -1); | ||
} | } | ||
} | } | ||
|
|
||
|
|||
/* | /* | ||
* decodes the bcrypt base 64 encoded SaltString | * decodes the bcrypt base 64 encoded SaltString | ||
* | * | ||
|
1 comment
on commit 00dfe74
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, the original commit is not suspicious, it's just totally wrong. The problem with the bug is it still "appears" to work, it's only when you look at the code it's obvious that it's not correct.
People are saying this loop is suspicious.