Skip to content

Commit 1946e04

Browse files
committed
#99: md5 and pbkdf2 conversion to operation on bytes
1 parent 004b8fe commit 1946e04

File tree

7 files changed

+156
-61
lines changed

7 files changed

+156
-61
lines changed

src/main/java/com/password4j/BcryptFunction.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public static BcryptFunction getInstanceFromHash(String hashed)
234234
else
235235
{
236236
char minor = hashed.charAt(2);
237-
if (!isValidMinor(minor) || hashed.charAt(3) != '$')
237+
if (isNotValidMinor(minor) || hashed.charAt(3) != '$')
238238
throw new BadParametersException("Invalid salt revision");
239239
int rounds = Integer.parseInt(hashed.substring(4, 6));
240240
return getInstance(Bcrypt.valueOf(minor), rounds);
@@ -430,9 +430,9 @@ protected static int streamToWordMinorX(byte[] data, int[] offp)
430430
return streamToWords(data, offp, signp)[1];
431431
}
432432

433-
private static boolean isValidMinor(char minor)
433+
private static boolean isNotValidMinor(char minor)
434434
{
435-
return Bcrypt.valueOf(minor) != null;
435+
return Bcrypt.valueOf(minor) == null;
436436
}
437437

438438
private static void internalChecks(String salt)
@@ -751,7 +751,7 @@ protected Hash hash(byte[] passwordb, String salt)
751751
else
752752
{
753753
minor = salt.charAt(2);
754-
if (!isValidMinor(minor) || salt.charAt(3) != '$')
754+
if (isNotValidMinor(minor) || salt.charAt(3) != '$')
755755
throw new BadParametersException("Invalid salt revision");
756756
off = 4;
757757
}

src/main/java/com/password4j/CompressedPBKDF2Function.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,13 @@ public static CompressedPBKDF2Function getInstanceFromHash(String hashed)
146146
throw new BadParametersException("`" + hashed + "` is not a valid hash");
147147
}
148148

149-
protected static String[] getParts(String hashed)
150-
{
151-
return hashed.split(new StringBuilder(2).append('\\').append(DELIMITER).toString());
152-
}
149+
153150

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

177+
protected static String[] getParts(String hashed)
178+
{
179+
return hashed.split(new StringBuilder(2).append('\\').append(DELIMITER).toString());
180+
}
181+
180182
private String getSaltFromHash(String hashed)
181183
{
182184
String[] parts = getParts(hashed);

src/main/java/com/password4j/Hash.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
* is used to verify the plain password; in addition <i>cryptographic
2929
* seasoning</i> such as salt and pepper are stored in this object.
3030
* <p>
31-
* An hash is the product of a one-way function that maps data of arbitrary size to
31+
* A hash is the product of a one-way function that maps data of arbitrary size to
3232
* fixed-size values; it is called hashing function (HF).
3333
* This class represent hashes generated by cryptographic hash function (CHF),
3434
* where each function has the following properties:
@@ -78,7 +78,7 @@ public class Hash
7878
* Represents the salt: random data that is used as an additional input
7979
* to a cryptographic hashing function.
8080
*/
81-
private String salt;
81+
private byte[] salt;
8282

8383
/**
8484
* Represents the pepper: a secret added to the input password
@@ -122,6 +122,29 @@ private Hash()
122122
* @since 0.1.0
123123
*/
124124
public Hash(HashingFunction hashingFunction, String result, byte[] bytes, String salt)
125+
{
126+
this.hashingFunction = hashingFunction;
127+
this.salt = Utils.fromCharSequenceToBytes(salt);
128+
this.result = result;
129+
this.bytes = bytes;
130+
}
131+
132+
/**
133+
* Constructs an {@link Hash} containing the basic information
134+
* used and produced by the computational process of hashing a password.
135+
* Other information, like <i>pepper</i> can be added with
136+
* {@link #setPepper(CharSequence)}.
137+
* <p>
138+
* This constructor populates the object's attributes.
139+
*
140+
* @param hashingFunction the cryptographic algorithm used to produce the hash.
141+
* @param result the result of the computation of the hash.
142+
* Notice that the format varies depending on the algorithm.
143+
* @param bytes the hash without additional information.
144+
* @param salt the salt used for the computation.
145+
* @since 0.1.0
146+
*/
147+
public Hash(HashingFunction hashingFunction, String result, byte[] bytes, byte[] salt)
125148
{
126149
this.hashingFunction = hashingFunction;
127150
this.salt = salt;
@@ -167,10 +190,21 @@ public HashingFunction getHashingFunction()
167190
/**
168191
* Retrieves the salt used by the hashing function.
169192
*
170-
* @return the salt.
193+
* @return the salt as {@link String}.
171194
* @since 0.1.0
172195
*/
173196
public String getSalt()
197+
{
198+
return Utils.fromBytesToString(salt);
199+
}
200+
201+
/**
202+
* Retrieves the salt used by the hashing function.
203+
*
204+
* @return the salt as bytes array.
205+
* @since 1.7.0
206+
*/
207+
public byte[] getSaltBytes()
174208
{
175209
return salt;
176210
}
@@ -200,7 +234,7 @@ void setPepper(CharSequence pepper)
200234
}
201235

202236
/**
203-
* Produces a human readable description of the {@link Hash}.
237+
* Produces a human-readable description of the {@link Hash}.
204238
*
205239
* @return a readable version of this object
206240
* @since 0.1.0
@@ -243,7 +277,7 @@ private boolean hasSameValues(Hash otherHash)
243277
{
244278
return areEquals(this.result, otherHash.result) //
245279
&& Arrays.equals(this.bytes, otherHash.bytes) //
246-
&& areEquals(this.salt, otherHash.salt) //
280+
&& Arrays.equals(this.salt, otherHash.salt) //
247281
&& areEquals(this.pepper, otherHash.pepper) //
248282
&& this.hashingFunction.equals(otherHash.hashingFunction);
249283
}
@@ -264,6 +298,6 @@ else if (cs1 != null && cs2 != null)
264298
@Override
265299
public int hashCode()
266300
{
267-
return Objects.hash(result, salt, pepper, hashingFunction);
301+
return Objects.hash(result, Arrays.hashCode(salt), pepper, hashingFunction);
268302
}
269303
}

src/main/java/com/password4j/MessageDigestFunction.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,33 @@ protected static String toString(String algorithm, SaltOption saltOption)
9696
@Override
9797
public Hash hash(CharSequence plainTextPassword)
9898
{
99-
return internalHash(plainTextPassword, null);
99+
return hash(plainTextPassword, null);
100+
}
101+
102+
public Hash hash(byte[] plainTextPasswordAsBytes)
103+
{
104+
return hash(plainTextPasswordAsBytes, null);
100105
}
101106

102107
@Override
103108
public Hash hash(CharSequence plainTextPassword, String salt)
104109
{
105-
return internalHash(plainTextPassword, salt);
110+
return internalHash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
111+
}
112+
113+
public Hash hash(byte[] plainTextPasswordAsBytes, byte[] saltAsBytes)
114+
{
115+
return internalHash(plainTextPasswordAsBytes, saltAsBytes);
106116
}
107117

108-
protected Hash internalHash(CharSequence plainTextPassword, String salt)
118+
protected Hash internalHash(byte[] plainTextPassword, byte[] salt)
109119
{
110120
try
111121
{
112122
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
113-
CharSequence finalCharSequence = concatenateSalt(plainTextPassword, salt);
123+
byte[] finalCharSequence = concatenateSalt(plainTextPassword, salt);
114124

115-
byte[] result = messageDigest.digest(Utils.fromCharSequenceToBytes(finalCharSequence));
125+
byte[] result = messageDigest.digest(finalCharSequence);
116126
return new Hash(this, Utils.toHex(result), result, salt);
117127
}
118128
catch (NoSuchAlgorithmException nsae)
@@ -124,17 +134,27 @@ protected Hash internalHash(CharSequence plainTextPassword, String salt)
124134
@Override
125135
public boolean check(CharSequence plainTextPassword, String hashed)
126136
{
127-
Hash hash = internalHash(plainTextPassword, null);
128-
return slowEquals(hash.getResult(), hashed);
137+
return check(plainTextPassword, hashed, null);
138+
}
139+
140+
public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed)
141+
{
142+
return check(plainTextPasswordAsBytes, hashed, null);
129143
}
130144

131145
@Override
132146
public boolean check(CharSequence plainTextPassword, String hashed, String salt)
133147
{
134-
Hash hash = internalHash(plainTextPassword, salt);
148+
Hash hash = internalHash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
135149
return slowEquals(hash.getResult(), hashed);
136150
}
137151

152+
public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
153+
{
154+
Hash hash = internalHash(plainTextPassword, salt);
155+
return slowEquals(hash.getBytes(), hashed);
156+
}
157+
138158
/**
139159
* The salt option describes if the Salt is appended or prepended to
140160
* the plain text password.
@@ -158,7 +178,7 @@ public String getAlgorithm()
158178
return algorithm;
159179
}
160180

161-
private CharSequence concatenateSalt(CharSequence plainTextPassword, CharSequence salt)
181+
private byte[] concatenateSalt(byte[] plainTextPassword, byte[] salt)
162182
{
163183
if (saltOption == SaltOption.PREPEND)
164184
{

src/main/java/com/password4j/PBKDF2Function.java

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.crypto.spec.PBEKeySpec;
2424
import java.security.NoSuchAlgorithmException;
2525
import java.security.spec.InvalidKeySpecException;
26+
import java.util.Arrays;
2627
import java.util.Map;
2728
import java.util.Objects;
2829
import java.util.concurrent.ConcurrentHashMap;
@@ -113,44 +114,28 @@ public static PBKDF2Function getInstance(String algorithm, int iterations, int l
113114
}
114115
}
115116

116-
protected static SecretKey internalHash(CharSequence plainTextPassword, String salt, String algorithm, int iterations,
117-
int length) throws NoSuchAlgorithmException, InvalidKeySpecException
118-
{
119-
if (salt == null)
120-
{
121-
throw new IllegalArgumentException("Salt cannot be null");
122-
}
123-
return internalHash(Utils.fromCharSequenceToChars(plainTextPassword), Utils.fromCharSequenceToBytes(salt), algorithm,
124-
iterations, length);
125-
}
126117

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

135-
protected static String getUID(String algorithm, int iterations, int length)
119+
@Override
120+
public Hash hash(CharSequence plainTextPassword)
136121
{
137-
return algorithm + "|" + iterations + "|" + length;
122+
byte[] salt = SaltGenerator.generate();
123+
return hash(Utils.fromCharSequenceToBytes(plainTextPassword), salt);
138124
}
139125

140-
protected static String toString(String algorithm, int iterations, int length)
126+
public Hash hash(byte[] plainTextPasswordAsBytes)
141127
{
142-
return "a=" + algorithm + ", i=" + iterations + ", l=" + length;
128+
byte[] salt = SaltGenerator.generate();
129+
return hash(plainTextPasswordAsBytes, salt);
143130
}
144131

145132
@Override
146-
public Hash hash(CharSequence plainTextPassword)
133+
public Hash hash(CharSequence plainTextPassword, String salt)
147134
{
148-
byte[] salt = SaltGenerator.generate();
149-
return hash(plainTextPassword, Utils.fromBytesToString(salt));
135+
return hash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
150136
}
151137

152-
@Override
153-
public Hash hash(CharSequence plainTextPassword, String salt)
138+
public Hash hash(byte[] plainTextPassword, byte[] salt)
154139
{
155140
try
156141
{
@@ -165,38 +150,79 @@ public Hash hash(CharSequence plainTextPassword, String salt)
165150
}
166151
catch (IllegalArgumentException | InvalidKeySpecException e)
167152
{
168-
String message = "Invalid specification with salt=" + salt + ", #iterations=" + iterations + " and length=" + length;
153+
String message = "Invalid specification with salt=" + Arrays.toString(salt) + ", #iterations=" + iterations + " and length=" + length;
169154
throw new BadParametersException(message, e);
170155
}
171156
}
172157

158+
protected static SecretKey internalHash(byte[] plainTextPassword, byte[] salt, String algorithm, int iterations, int length) throws NoSuchAlgorithmException, InvalidKeySpecException
159+
{
160+
if (salt == null)
161+
{
162+
throw new IllegalArgumentException("Salt cannot be null");
163+
}
164+
return internalHash(Utils.fromBytesToChars(plainTextPassword), salt, algorithm, iterations, length);
165+
}
166+
167+
protected static SecretKey internalHash(char[] plain, byte[] salt, String algorithm, int iterations, int length)
168+
throws NoSuchAlgorithmException, InvalidKeySpecException
169+
{
170+
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(ALGORITHM_PREFIX + algorithm);
171+
PBEKeySpec spec = new PBEKeySpec(plain, salt, iterations, length);
172+
return secretKeyFactory.generateSecret(spec);
173+
}
174+
175+
protected static String getUID(String algorithm, int iterations, int length)
176+
{
177+
return algorithm + "|" + iterations + "|" + length;
178+
}
179+
180+
protected static String toString(String algorithm, int iterations, int length)
181+
{
182+
return "a=" + algorithm + ", i=" + iterations + ", l=" + length;
183+
}
184+
185+
186+
173187
/**
174188
* Overridable PBKDF2 generator
175189
*
176190
* @param encodedKey secret encodedKey
177191
* @param salt cryptographic salt
178192
* @return the PBKDF2 hash string
179193
*/
180-
protected String getHash(byte[] encodedKey, String salt)
194+
protected String getHash(byte[] encodedKey, byte[] salt)
181195
{
182196
return Utils.encodeBase64(encodedKey);
183197
}
184198

199+
@Override
200+
public boolean check(CharSequence plainTextPassword, String hashed)
201+
{
202+
return check((byte[]) null, null);
203+
}
204+
205+
public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed)
206+
{
207+
throw new UnsupportedOperationException("This implementation requires an explicit salt.");
208+
209+
}
210+
185211
@Override
186212
public boolean check(CharSequence plainTextPassword, String hashed, String salt)
187213
{
188214
Hash internalHash = hash(plainTextPassword, salt);
189215
return slowEquals(internalHash.getResult(), hashed);
190216
}
191217

192-
@Override
193-
public boolean check(CharSequence plainTextPassword, String hashed)
218+
public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed, byte[] salt)
194219
{
195-
throw new UnsupportedOperationException(
196-
"This implementation requires an explicit salt. Use check(CharSequence, String, String) method instead.");
197-
220+
Hash internalHash = hash(plainTextPasswordAsBytes, salt);
221+
return slowEquals(internalHash.getBytes(), hashed);
198222
}
199223

224+
225+
200226
public String getAlgorithm()
201227
{
202228
return algorithmAsString;

0 commit comments

Comments
 (0)