Skip to content

Commit

Permalink
#99: md5 and pbkdf2 conversion to operation on bytes
Browse files Browse the repository at this point in the history
  • Loading branch information
firaja committed Feb 15, 2023
1 parent 004b8fe commit 1946e04
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 61 deletions.
8 changes: 4 additions & 4 deletions src/main/java/com/password4j/BcryptFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public static BcryptFunction getInstanceFromHash(String hashed)
else
{
char minor = hashed.charAt(2);
if (!isValidMinor(minor) || hashed.charAt(3) != '$')
if (isNotValidMinor(minor) || hashed.charAt(3) != '$')
throw new BadParametersException("Invalid salt revision");
int rounds = Integer.parseInt(hashed.substring(4, 6));
return getInstance(Bcrypt.valueOf(minor), rounds);
Expand Down Expand Up @@ -430,9 +430,9 @@ protected static int streamToWordMinorX(byte[] data, int[] offp)
return streamToWords(data, offp, signp)[1];
}

private static boolean isValidMinor(char minor)
private static boolean isNotValidMinor(char minor)
{
return Bcrypt.valueOf(minor) != null;
return Bcrypt.valueOf(minor) == null;
}

private static void internalChecks(String salt)
Expand Down Expand Up @@ -751,7 +751,7 @@ protected Hash hash(byte[] passwordb, String salt)
else
{
minor = salt.charAt(2);
if (!isValidMinor(minor) || salt.charAt(3) != '$')
if (isNotValidMinor(minor) || salt.charAt(3) != '$')
throw new BadParametersException("Invalid salt revision");
off = 4;
}
Expand Down
14 changes: 8 additions & 6 deletions src/main/java/com/password4j/CompressedPBKDF2Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,16 +146,13 @@ public static CompressedPBKDF2Function getInstanceFromHash(String hashed)
throw new BadParametersException("`" + hashed + "` is not a valid hash");
}

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


@Override
protected String getHash(byte[] encodedKey, String salt)
protected String getHash(byte[] encodedKey, byte[] salt)
{
String params = Long.toString((((long) getIterations()) << 32) | (getLength() & 0xffffffffL));
String salt64 = Utils.encodeBase64(Utils.fromCharSequenceToBytes(salt));
String salt64 = Utils.encodeBase64(salt);
String hash64 = super.getHash(encodedKey, salt);
return "$" + algorithm.code() + "$" + params + "$" + salt64 + "$" + hash64;
}
Expand All @@ -177,6 +174,11 @@ public boolean check(CharSequence plainTextPassword, String hashed, String salt)
return slowEquals(internalHas.getResult(), hashed);
}

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

private String getSaltFromHash(String hashed)
{
String[] parts = getParts(hashed);
Expand Down
46 changes: 40 additions & 6 deletions src/main/java/com/password4j/Hash.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* is used to verify the plain password; in addition <i>cryptographic
* seasoning</i> such as salt and pepper are stored in this object.
* <p>
* An hash is the product of a one-way function that maps data of arbitrary size to
* A hash is the product of a one-way function that maps data of arbitrary size to
* fixed-size values; it is called hashing function (HF).
* This class represent hashes generated by cryptographic hash function (CHF),
* where each function has the following properties:
Expand Down Expand Up @@ -78,7 +78,7 @@ public class Hash
* Represents the salt: random data that is used as an additional input
* to a cryptographic hashing function.
*/
private String salt;
private byte[] salt;

/**
* Represents the pepper: a secret added to the input password
Expand Down Expand Up @@ -122,6 +122,29 @@ private Hash()
* @since 0.1.0
*/
public Hash(HashingFunction hashingFunction, String result, byte[] bytes, String salt)
{
this.hashingFunction = hashingFunction;
this.salt = Utils.fromCharSequenceToBytes(salt);
this.result = result;
this.bytes = bytes;
}

/**
* Constructs an {@link Hash} containing the basic information
* used and produced by the computational process of hashing a password.
* Other information, like <i>pepper</i> can be added with
* {@link #setPepper(CharSequence)}.
* <p>
* This constructor populates the object's attributes.
*
* @param hashingFunction the cryptographic algorithm used to produce the hash.
* @param result the result of the computation of the hash.
* Notice that the format varies depending on the algorithm.
* @param bytes the hash without additional information.
* @param salt the salt used for the computation.
* @since 0.1.0
*/
public Hash(HashingFunction hashingFunction, String result, byte[] bytes, byte[] salt)
{
this.hashingFunction = hashingFunction;
this.salt = salt;
Expand Down Expand Up @@ -167,10 +190,21 @@ public HashingFunction getHashingFunction()
/**
* Retrieves the salt used by the hashing function.
*
* @return the salt.
* @return the salt as {@link String}.
* @since 0.1.0
*/
public String getSalt()
{
return Utils.fromBytesToString(salt);
}

/**
* Retrieves the salt used by the hashing function.
*
* @return the salt as bytes array.
* @since 1.7.0
*/
public byte[] getSaltBytes()
{
return salt;
}
Expand Down Expand Up @@ -200,7 +234,7 @@ void setPepper(CharSequence pepper)
}

/**
* Produces a human readable description of the {@link Hash}.
* Produces a human-readable description of the {@link Hash}.
*
* @return a readable version of this object
* @since 0.1.0
Expand Down Expand Up @@ -243,7 +277,7 @@ private boolean hasSameValues(Hash otherHash)
{
return areEquals(this.result, otherHash.result) //
&& Arrays.equals(this.bytes, otherHash.bytes) //
&& areEquals(this.salt, otherHash.salt) //
&& Arrays.equals(this.salt, otherHash.salt) //
&& areEquals(this.pepper, otherHash.pepper) //
&& this.hashingFunction.equals(otherHash.hashingFunction);
}
Expand All @@ -264,6 +298,6 @@ else if (cs1 != null && cs2 != null)
@Override
public int hashCode()
{
return Objects.hash(result, salt, pepper, hashingFunction);
return Objects.hash(result, Arrays.hashCode(salt), pepper, hashingFunction);
}
}
38 changes: 29 additions & 9 deletions src/main/java/com/password4j/MessageDigestFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,23 +96,33 @@ protected static String toString(String algorithm, SaltOption saltOption)
@Override
public Hash hash(CharSequence plainTextPassword)
{
return internalHash(plainTextPassword, null);
return hash(plainTextPassword, null);
}

public Hash hash(byte[] plainTextPasswordAsBytes)
{
return hash(plainTextPasswordAsBytes, null);
}

@Override
public Hash hash(CharSequence plainTextPassword, String salt)
{
return internalHash(plainTextPassword, salt);
return internalHash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
}

public Hash hash(byte[] plainTextPasswordAsBytes, byte[] saltAsBytes)
{
return internalHash(plainTextPasswordAsBytes, saltAsBytes);
}

protected Hash internalHash(CharSequence plainTextPassword, String salt)
protected Hash internalHash(byte[] plainTextPassword, byte[] salt)
{
try
{
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
CharSequence finalCharSequence = concatenateSalt(plainTextPassword, salt);
byte[] finalCharSequence = concatenateSalt(plainTextPassword, salt);

byte[] result = messageDigest.digest(Utils.fromCharSequenceToBytes(finalCharSequence));
byte[] result = messageDigest.digest(finalCharSequence);
return new Hash(this, Utils.toHex(result), result, salt);
}
catch (NoSuchAlgorithmException nsae)
Expand All @@ -124,17 +134,27 @@ protected Hash internalHash(CharSequence plainTextPassword, String salt)
@Override
public boolean check(CharSequence plainTextPassword, String hashed)
{
Hash hash = internalHash(plainTextPassword, null);
return slowEquals(hash.getResult(), hashed);
return check(plainTextPassword, hashed, null);
}

public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed)
{
return check(plainTextPasswordAsBytes, hashed, null);
}

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

public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
{
Hash hash = internalHash(plainTextPassword, salt);
return slowEquals(hash.getBytes(), hashed);
}

/**
* The salt option describes if the Salt is appended or prepended to
* the plain text password.
Expand All @@ -158,7 +178,7 @@ public String getAlgorithm()
return algorithm;
}

private CharSequence concatenateSalt(CharSequence plainTextPassword, CharSequence salt)
private byte[] concatenateSalt(byte[] plainTextPassword, byte[] salt)
{
if (saltOption == SaltOption.PREPEND)
{
Expand Down
92 changes: 59 additions & 33 deletions src/main/java/com/password4j/PBKDF2Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -113,44 +114,28 @@ public static PBKDF2Function getInstance(String algorithm, int iterations, int l
}
}

protected static SecretKey internalHash(CharSequence plainTextPassword, String salt, String algorithm, int iterations,
int length) throws NoSuchAlgorithmException, InvalidKeySpecException
{
if (salt == null)
{
throw new IllegalArgumentException("Salt cannot be null");
}
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)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(ALGORITHM_PREFIX + algorithm);
PBEKeySpec spec = new PBEKeySpec(plain, salt, iterations, length);
return secretKeyFactory.generateSecret(spec);
}

protected static String getUID(String algorithm, int iterations, int length)
@Override
public Hash hash(CharSequence plainTextPassword)
{
return algorithm + "|" + iterations + "|" + length;
byte[] salt = SaltGenerator.generate();
return hash(Utils.fromCharSequenceToBytes(plainTextPassword), salt);
}

protected static String toString(String algorithm, int iterations, int length)
public Hash hash(byte[] plainTextPasswordAsBytes)
{
return "a=" + algorithm + ", i=" + iterations + ", l=" + length;
byte[] salt = SaltGenerator.generate();
return hash(plainTextPasswordAsBytes, salt);
}

@Override
public Hash hash(CharSequence plainTextPassword)
public Hash hash(CharSequence plainTextPassword, String salt)
{
byte[] salt = SaltGenerator.generate();
return hash(plainTextPassword, Utils.fromBytesToString(salt));
return hash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
}

@Override
public Hash hash(CharSequence plainTextPassword, String salt)
public Hash hash(byte[] plainTextPassword, byte[] salt)
{
try
{
Expand All @@ -165,38 +150,79 @@ public Hash hash(CharSequence plainTextPassword, String salt)
}
catch (IllegalArgumentException | InvalidKeySpecException e)
{
String message = "Invalid specification with salt=" + 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);
}
}

protected static SecretKey internalHash(byte[] plainTextPassword, byte[] salt, String algorithm, int iterations, int length) throws NoSuchAlgorithmException, InvalidKeySpecException
{
if (salt == null)
{
throw new IllegalArgumentException("Salt cannot be null");
}
return internalHash(Utils.fromBytesToChars(plainTextPassword), salt, algorithm, iterations, length);
}

protected static SecretKey internalHash(char[] plain, byte[] salt, String algorithm, int iterations, int length)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(ALGORITHM_PREFIX + algorithm);
PBEKeySpec spec = new PBEKeySpec(plain, salt, iterations, length);
return secretKeyFactory.generateSecret(spec);
}

protected static String getUID(String algorithm, int iterations, int length)
{
return algorithm + "|" + iterations + "|" + length;
}

protected static String toString(String algorithm, int iterations, int length)
{
return "a=" + algorithm + ", i=" + iterations + ", l=" + length;
}



/**
* Overridable PBKDF2 generator
*
* @param encodedKey secret encodedKey
* @param salt cryptographic salt
* @return the PBKDF2 hash string
*/
protected String getHash(byte[] encodedKey, String salt)
protected String getHash(byte[] encodedKey, byte[] salt)
{
return Utils.encodeBase64(encodedKey);
}

@Override
public boolean check(CharSequence plainTextPassword, String hashed)
{
return check((byte[]) null, null);
}

public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed)
{
throw new UnsupportedOperationException("This implementation requires an explicit salt.");

}

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

@Override
public boolean check(CharSequence plainTextPassword, String hashed)
public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed, byte[] salt)
{
throw new UnsupportedOperationException(
"This implementation requires an explicit salt. Use check(CharSequence, String, String) method instead.");

Hash internalHash = hash(plainTextPasswordAsBytes, salt);
return slowEquals(internalHash.getBytes(), hashed);
}



public String getAlgorithm()
{
return algorithmAsString;
Expand Down

0 comments on commit 1946e04

Please sign in to comment.