Skip to content

Commit

Permalink
#99: first migration to bytes operation
Browse files Browse the repository at this point in the history
  • Loading branch information
firaja committed Feb 17, 2023
1 parent 89708af commit 82e8a07
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 22 deletions.
45 changes: 34 additions & 11 deletions src/main/java/com/password4j/CompressedPBKDF2Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.password4j.types.Hmac;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

Expand Down Expand Up @@ -160,32 +161,54 @@ protected String getHash(byte[] encodedKey, byte[] salt)
@Override
public boolean check(CharSequence plainTextPassword, String hashed)
{
String salt = getSaltFromHash(hashed);
return check(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(hashed));
}

@Override
public boolean check(byte[] plainTextPassword, byte[] hashed)
{
byte[] salt = getSaltFromHash(hashed);
Hash internalHas = hash(plainTextPassword, salt);

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

@Override
public boolean check(CharSequence plainTextPassword, String hashed, String salt)
{
String realSalt = getSaltFromHash(hashed);
Hash internalHas = hash(plainTextPassword, realSalt);
return slowEquals(internalHas.getResult(), hashed);
byte[] hashAsBytes = Utils.fromCharSequenceToBytes(hashed);
byte[] realSalt = getSaltFromHash(hashAsBytes);
byte[] plainTextPasswordAsBytes = Utils.fromCharSequenceToBytes(plainTextPassword);
Hash internalHash = hash(plainTextPasswordAsBytes, realSalt);
return slowEquals(internalHash.getResult(), hashed);
}

@Override
public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
{
byte[] realSalt = getSaltFromHash(hashed);
Hash internalHash = hash(plainTextPassword, realSalt);
return slowEquals(internalHash.getResultAsBytes(), hashed);
}

protected static List<byte[]> getParts(byte[] hashed)
{
return Utils.split(hashed, (byte) DELIMITER);
}

protected static String[] getParts(String hashed)
{
return hashed.split(new StringBuilder(2).append('\\').append(DELIMITER).toString());
String regex = "\\" + DELIMITER;
return hashed.split(regex);
}

private String getSaltFromHash(String hashed)
private byte[] getSaltFromHash(byte[] hashed)
{
String[] parts = getParts(hashed);
if (parts.length == 5)
List<byte[]> parts = getParts(hashed);
if (parts.size() == 5)
{
return Utils.fromBytesToString(Utils.decodeBase64(Utils.fromCharSequenceToBytes(parts[3])));
return Utils.decodeBase64(parts.get(3));
}
throw new BadParametersException("`" + hashed + "` is not a valid hash");
throw new BadParametersException("`" + Utils.fromBytesToString(hashed) + "` is not a valid hash");
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/password4j/HashChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public HashUpdater andUpdate()
*/
public boolean with(HashingFunction hashingFunction)
{
if (plainTextPassword == null)
if (plainTextPassword == null || plainTextPassword.length == 0)
{
return false;
}
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/password4j/HashUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,19 @@ public HashUpdater addNewSalt(String salt)
return this;
}

/**
* Adds new cryptographic salt to be applied in the hash update.
*
* @param saltAsBytes cryptographic salt as bytes array
* @return this builder
* @since 1.3.0
*/
public HashUpdater addNewSalt(byte[] saltAsBytes)
{
this.hashBuilder.addSalt(saltAsBytes);
return this;
}

/**
* Add a random cryptographic salt in the hash update process.
* The salt is applied differently depending on the new chosen algorithm.
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/password4j/MessageDigestFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public boolean check(CharSequence plainTextPassword, String hashed, String salt)
public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
{
Hash hash = internalHash(plainTextPassword, salt);
return slowEquals(hash.getBytes(), hashed);
return slowEquals(hash.getResultAsBytes(), hashed);
}

/**
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 @@ -152,7 +152,7 @@ public Hash hash(byte[] plainTextPassword, byte[] salt)
}
catch (IllegalArgumentException | InvalidKeySpecException e)
{
String message = "Invalid specification with salt=" + Arrays.toString(salt) + ", #iterations=" + iterations + " and length=" + length;
String message = "Invalid specification with salt=" + Arrays.toString(salt) + ", iterations=" + iterations + " and length=" + length;
throw new BadParametersException(message, e);
}
}
Expand Down Expand Up @@ -222,7 +222,7 @@ public boolean check(CharSequence plainTextPassword, String hashed, String salt)
public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed, byte[] salt)
{
Hash internalHash = hash(plainTextPasswordAsBytes, salt);
return slowEquals(internalHash.getBytes(), hashed);
return slowEquals(internalHash.getResultAsBytes(), hashed);
}


Expand Down
80 changes: 77 additions & 3 deletions src/main/java/com/password4j/Password.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,37 @@ public static HashBuilder hash(CharSequence plainTextPassword)
}

/**
* Starts to verify if an hash string has been generated with
* Starts to hash the given plain text password.
* <p>
* This method is used to start the setup of a {@link HashBuilder}
* instance that finally should execute the {@link HashBuilder#with(HashingFunction)}
* method to hash the password.
*
* @param plainTextPassword the plain text password as bytes array
* @return a builder instance of {@link HashBuilder}
* @throws BadParametersException if any of the arguments are null.
* @since 1.7.0
*/
public static HashBuilder hash(byte[] plainTextPassword)
{
if (plainTextPassword == null || plainTextPassword.length == 0)
{
throw new BadParametersException("Password cannot be null");
}
return new HashBuilder(plainTextPassword);
}


/**
* Starts to verify if a hash string has been generated with
* the given plain text password.
* <p>
* This method is used to start the setup of an {@link HashChecker}
* instance that finally should execute the {@link HashChecker#with(HashingFunction)}
* method to verify the hash.
*
* @param plainTextPassword the plain text password
* @param hash an hash string
* @param hash a hash string
* @return a builder instance of {@link HashChecker}
* @throws BadParametersException if any of the arguments are null.
* @since 0.1.1
Expand All @@ -86,8 +108,32 @@ public static HashChecker check(CharSequence plainTextPassword, String hash)
return new HashChecker(plainTextPassword, hash);
}


/**
* Starts to verify if an hash object has been generated with
* Starts to verify if a hash string has been generated with
* the given plain text password.
* <p>
* This method is used to start the setup of an {@link HashChecker}
* instance that finally should execute the {@link HashChecker#with(HashingFunction)}
* method to verify the hash.
*
* @param plainTextPassword the plain text password as bytes array
* @param hash a hash string as bytes array
* @return a builder instance of {@link HashChecker}
* @throws BadParametersException if any of the arguments are null.
* @since 1.7.0
*/
public static HashChecker check(byte[] plainTextPassword, byte[] hash)
{
if (hash == null || plainTextPassword == null || hash.length == 0 || plainTextPassword.length == 0)
{
throw new BadParametersException("Hash or plain cannot be null");
}
return new HashChecker(plainTextPassword, hash);
}

/**
* Starts to verify if a hash object has been generated with
* the given plain text password.
* <p>
* This method uses the {@link HashingFunction} used to calculate the given {@link Hash}.
Expand All @@ -114,4 +160,32 @@ public static boolean check(CharSequence plainTextPassword, Hash hashObject)
return hashObject.getHashingFunction().check(plainTextPassword, hashObject.getResult(), hashObject.getSalt(), hashObject.getPepper());
}

/**
* Starts to verify if a hash object has been generated with
* the given plain text password.
* <p>
* This method uses the {@link HashingFunction} used to calculate the given {@link Hash}.
* Il the password is null, this returns false;
* otherwise {@link HashingFunction#check(CharSequence, String)} is invoked.
*
* @param plainTextPassword the original password as bytes array.
* @param hashObject an {@link Hash} object.
* @return true if the check passes, false otherwise.
* @throws BadParametersException if the Hash is null or if there's no hashing function defined in it.
* @since 1.7.0
*/
public static boolean check(byte[] plainTextPassword, Hash hashObject)
{
if (hashObject == null || hashObject.getHashingFunction() == null)
{
throw new BadParametersException("Invalid Hash object. " + (hashObject != null ? hashObject.toString() : null));
}
if (plainTextPassword == null)
{
return false;
}

return hashObject.getHashingFunction().check(plainTextPassword, hashObject.getResultAsBytes(), hashObject.getResultAsBytes(), hashObject.getPepper());
}

}
2 changes: 1 addition & 1 deletion src/test/com/password4j/HashTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void testHashCheckNull()
Hash hash = Password.hash("myPassword").withCompressedPBKDF2();

// WHEN
boolean result = Password.check(null, hash);
boolean result = Password.check((byte[]) null, hash);

// THEN
Assert.assertFalse(result);
Expand Down
37 changes: 34 additions & 3 deletions src/test/com/password4j/PasswordTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
Expand Down Expand Up @@ -213,6 +214,36 @@ public void testRawUpdate5()
Assert.assertEquals(Password.hash(PASSWORD).addPepper("newpepper").addSalt("newsalt").withArgon2().getResult(), update.getHash().getResult());
}

@Test
public void testRawUpdate6()
{
// GIVEN
Hash hash = Password.hash(PASSWORD.getBytes(StandardCharsets.UTF_8)).addPepper(PEPPER).withBcrypt();

// WHEN
HashUpdate update = Password.check(PASSWORD.getBytes(StandardCharsets.UTF_8), hash.getResultAsBytes()).addPepper(PEPPER).addSalt(SALT.getBytes(StandardCharsets.UTF_8))
.andUpdate().addNewSalt("$2a$07$W3mOfB5auMDG3EitumH0S.").addNewPepper("newpepper").withBcrypt();

// THEN
assertTrue(update.isVerified());
Assert.assertEquals(Password.hash(PASSWORD.getBytes(StandardCharsets.UTF_8)).addPepper("newpepper").addSalt("$2a$07$W3mOfB5auMDG3EitumH0S.").withBcrypt().getResult(), update.getHash().getResult());
}

@Test
public void testRawUpdate7()
{
// GIVEN
Hash hash = Password.hash(PASSWORD).addPepper(PEPPER).addSalt(SALT).withScrypt();

// WHEN
HashUpdate update = Password.check(PASSWORD, hash.getResult()).addPepper(PEPPER).addSalt(SALT)
.andUpdate().addNewSalt("newsalt").addNewPepper("newpepper").withScrypt();

// THEN
assertTrue(update.isVerified());
Assert.assertEquals(Password.hash(PASSWORD).addPepper("newpepper").addSalt("newsalt").withScrypt().getResult(), update.getHash().getResult());
}



@Test
Expand Down Expand Up @@ -351,7 +382,7 @@ public void testHashingFunction()
@Test(expected = BadParametersException.class)
public void testBad1()
{
Password.hash(null);
Password.hash((byte[]) null);
}

@Test(expected = BadParametersException.class)
Expand Down Expand Up @@ -385,7 +416,7 @@ public void testBad6()
@Test(expected = BadParametersException.class)
public void testBad7()
{
Password.check(null, (Hash)null);
Password.check((CharSequence) null, (Hash)null);
}

@Test(expected = BadParametersException.class)
Expand Down Expand Up @@ -925,7 +956,7 @@ public void issue92()
Argon2Function function = Argon2Function.getInstanceFromHash(hash);

boolean verified = Password.check(plain, hash).with(function);
Hash newHash = Password.hash(plain).addSalt("Y9ΫI2o.W").with(function);
Hash newHash = Password.hash(plain).addSalt("Y9ΫI2o.W".getBytes(StandardCharsets.UTF_8)).with(function);
boolean verified2 = Password.check(plain, newHash);

assertTrue(verified);
Expand Down

0 comments on commit 82e8a07

Please sign in to comment.