Skip to content

Commit

Permalink
Merge pull request #27 from Password4j/bug-26
Browse files Browse the repository at this point in the history
#26: Strings are always converted to byte[]  with the same encoder
  • Loading branch information
firaja authored Feb 4, 2021
2 parents 423aff9 + e079318 commit 750b49b
Show file tree
Hide file tree
Showing 17 changed files with 314 additions and 84 deletions.
14 changes: 14 additions & 0 deletions src/main/java/com/password4j/AbstractHashingFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ public boolean check(CharSequence plainTextPassword, String hashed, String salt,
return check(Utils.append(pepper, plainTextPassword), hashed, salt);
}

/**
* Compares two {@link CharSequence}s as byte arrays in length-constant time. This comparison method
* is used so that password hashes cannot be extracted from an on-line
* system using a timing attack and then attacked off-line.
*
* @param a the first CharSequence
* @param b the second CharSequence
* @return true if both {@link CharSequence}s are the same, false if not
*/
protected static boolean slowEquals(CharSequence a, CharSequence b)
{
return slowEquals(Utils.fromCharSequenceToBytes(a), Utils.fromCharSequenceToBytes(b));
}

/**
* Compares two byte arrays in length-constant time. This comparison method
* is used so that password hashes cannot be extracted from an on-line
Expand Down
52 changes: 28 additions & 24 deletions src/main/java/com/password4j/AlgorithmFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,32 +57,11 @@ public class AlgorithmFinder
* @see #getSecureRandom()
* @since 0.1.0
*/
private static final SecureRandom SR_SOURCE;
private static SecureRandom secureRandom;

static
{
SecureRandom sr;
if (useStrongRandom())
{
try
{
sr = SecureRandom.getInstanceStrong();
}
catch (NoSuchAlgorithmException nsae)
{
/* Even if there's no strong instance, execution
* must continue with a less strong SecureRandom instance */
LOG.warn("No source of strong randomness found for this environment.");
sr = new SecureRandom();
}

}
else
{
sr = new SecureRandom();
}
SR_SOURCE = sr;

initialize();
}

private AlgorithmFinder()
Expand All @@ -109,7 +88,7 @@ private AlgorithmFinder()
*/
public static SecureRandom getSecureRandom()
{
return SR_SOURCE;
return secureRandom;
}

/**
Expand Down Expand Up @@ -381,6 +360,31 @@ private static boolean useStrongRandom()
return PropertyReader.readBoolean("global.random.strong", false);
}

static void initialize()
{
SecureRandom sr;
if (useStrongRandom())
{
try
{
sr = SecureRandom.getInstanceStrong();
}
catch (NoSuchAlgorithmException nsae)
{
/* Even if there's no strong instance, execution
* must continue with a less strong SecureRandom instance */
LOG.warn("No source of strong randomness found for this environment.");
sr = new SecureRandom();
}

}
else
{
sr = new SecureRandom();
}
secureRandom = sr;
}

private static class Param
{
String algorithm;
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/password4j/Argon2Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public Hash hash(CharSequence plainTextPassword, String salt, CharSequence peppe
{
theSalt = salt;
}
initialize(password, theSalt.getBytes(), Utils.fromCharSequenceToBytes(pepper), null, blockMemory);
initialize(password, Utils.fromCharSequenceToBytes(theSalt), Utils.fromCharSequenceToBytes(pepper), null, blockMemory);
fillMemoryBlocks(blockMemory);
byte[] hash = ending(blockMemory);
return new Hash(this, encodeHash(hash, theSalt), theSalt);
Expand Down Expand Up @@ -190,7 +190,7 @@ public boolean check(CharSequence plainTextPassword, String hashed, String salt,
}

Hash internalHash = hash(plainTextPassword, theSalt, pepper);
return slowEquals(internalHash.getResult().getBytes(), hashed.getBytes());
return slowEquals(internalHash.getResult(), hashed);
}


Expand Down Expand Up @@ -713,7 +713,8 @@ private long[][] copyOf(long[][] old)
private String encodeHash(byte[] hash, String salt)
{
return "$argon2" + variant.name().toLowerCase() + "$v=" + version + "$m=" + memory
+ ",t=" + iterations + ",p=" + parallelism + "$" + Base64.getEncoder().withoutPadding().encodeToString(salt.getBytes()) +
+ ",t=" + iterations + ",p=" + parallelism +
"$" + Base64.getEncoder().withoutPadding().encodeToString(Utils.fromCharSequenceToBytes(salt)) +
"$" + Base64.getEncoder().withoutPadding().encodeToString(hash);
}

Expand All @@ -732,7 +733,6 @@ private static Object[] decodeHash(String hash)
result[4] = Integer.parseInt(StringUtils.removeStart(params[2], "p="));
result[5] = Base64.getDecoder().decode(parts[4]);
result[6] = Base64.getDecoder().decode(parts[5]);

return result;
}
else
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/password4j/BCryptFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,7 @@ protected boolean checkPw(CharSequence plaintext, String hashed)

static boolean equalsNoEarlyReturn(String a, String b)
{
return MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8));
return MessageDigest.isEqual(Utils.fromCharSequenceToBytes(a), Utils.fromCharSequenceToBytes(b));
}

}
8 changes: 4 additions & 4 deletions src/main/java/com/password4j/CompressedPBKDF2Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public static CompressedPBKDF2Function getInstanceFromHash(String hashed)
protected String getHash(SecretKey key, String salt)
{
String params = Long.toString((((long) getIterations()) << 32) | (getLength() & 0xffffffffL));
String salt64 = Base64.getEncoder().encodeToString(salt.getBytes());
String salt64 = Base64.getEncoder().encodeToString(Utils.fromCharSequenceToBytes(salt));
String hash64 = super.getHash(key, salt);
return "$" + algorithm.code() + "$" + params + "$" + salt64 + "$" + hash64;
}
Expand All @@ -164,23 +164,23 @@ public boolean check(CharSequence plainTextPassword, String hashed)
String salt = getSaltFromHash(hashed);
Hash internalHas = hash(plainTextPassword, salt);

return slowEquals(internalHas.getResult().getBytes(), hashed.getBytes());
return slowEquals(internalHas.getResult(), hashed);
}

@Override
public boolean check(CharSequence plainTextPassword, String hashed, String salt)
{
String realSalt = getSaltFromHash(hashed);
Hash internalHas = hash(plainTextPassword, realSalt);
return slowEquals(internalHas.getResult().getBytes(), hashed.getBytes());
return slowEquals(internalHas.getResult(), hashed);
}

private String getSaltFromHash(String hashed)
{
String[] parts = getParts(hashed);
if (parts.length == 5)
{
return new String(Base64.getDecoder().decode(parts[3].getBytes()));
return new String(Base64.getDecoder().decode(Utils.fromCharSequenceToBytes(parts[3])));
}
throw new BadParametersException("`" + hashed + "` is not a valid hash");
}
Expand Down
16 changes: 14 additions & 2 deletions src/main/java/com/password4j/MessageDigestFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,26 @@ protected Hash internalHash(CharSequence plainTextPassword, String salt)
public boolean check(CharSequence plainTextPassword, String hashed)
{
Hash hash = internalHash(plainTextPassword, null);
return slowEquals(hash.getResult().getBytes(), hashed.getBytes());
return slowEquals(hash.getResult(), hashed);
}

@Override
public boolean check(CharSequence plainTextPassword, String hashed, String salt)
{
Hash hash = internalHash(plainTextPassword, salt);
return slowEquals(hash.getResult().getBytes(), hashed.getBytes());
return slowEquals(hash.getResult(), hashed);
}

/**
* The salt option describes if the Salt has been appended or prepended to
* the plain test password.
*
* @return how the salt is concatenated
* @since 1.6.1
*/
public SaltOption getSaltOption()
{
return saltOption;
}

private CharSequence concatenateSalt(CharSequence plainTextPassword, CharSequence salt)
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/password4j/PBKDF2Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ protected static SecretKey internalHash(CharSequence plainTextPassword, String s
{
throw new IllegalArgumentException("Salt cannot be null");
}
return internalHash(Utils.fromCharSequenceToChars(plainTextPassword), salt.getBytes(), algorithm, iterations, length);
return internalHash(Utils.fromCharSequenceToChars(plainTextPassword), Utils.fromCharSequenceToBytes(salt), algorithm, iterations, length);
}

protected static SecretKey internalHash(char[] plain, byte[] salt, String algorithm, int iterations, int length)
Expand All @@ -173,7 +173,7 @@ protected String getHash(SecretKey key, String salt)
public boolean check(CharSequence plainTextPassword, String hashed, String salt)
{
Hash internalHash = hash(plainTextPassword, salt);
return slowEquals(internalHash.getResult().getBytes(), hashed.getBytes());
return slowEquals(internalHash.getResult(), hashed);
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/password4j/SCryptFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public Hash hash(CharSequence plainTextPassword, String salt)
{
try
{
byte[] saltAsBytes = salt.getBytes(StandardCharsets.UTF_8);
byte[] saltAsBytes = Utils.fromCharSequenceToBytes(salt);
byte[] derived = scrypt(Utils.fromCharSequenceToBytes(plainTextPassword), saltAsBytes, 64);
String params = Long.toString((long) Utils.log2(workFactor) << 16 | (long) resources << 8 | parallelization, 16);
String sb = "$s0$" + params + '$' + Base64.getEncoder().encodeToString(saltAsBytes) + '$' + Base64.getEncoder()
Expand Down
Loading

0 comments on commit 750b49b

Please sign in to comment.