diff --git a/src/main/java/com/password4j/AbstractHashingFunction.java b/src/main/java/com/password4j/AbstractHashingFunction.java index e5927396..540a2ff7 100644 --- a/src/main/java/com/password4j/AbstractHashingFunction.java +++ b/src/main/java/com/password4j/AbstractHashingFunction.java @@ -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 diff --git a/src/main/java/com/password4j/AlgorithmFinder.java b/src/main/java/com/password4j/AlgorithmFinder.java index 67381b4f..801f9978 100644 --- a/src/main/java/com/password4j/AlgorithmFinder.java +++ b/src/main/java/com/password4j/AlgorithmFinder.java @@ -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() @@ -109,7 +88,7 @@ private AlgorithmFinder() */ public static SecureRandom getSecureRandom() { - return SR_SOURCE; + return secureRandom; } /** @@ -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; diff --git a/src/main/java/com/password4j/Argon2Function.java b/src/main/java/com/password4j/Argon2Function.java index 08332103..9022ccc1 100644 --- a/src/main/java/com/password4j/Argon2Function.java +++ b/src/main/java/com/password4j/Argon2Function.java @@ -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); @@ -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); } @@ -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); } @@ -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 diff --git a/src/main/java/com/password4j/BCryptFunction.java b/src/main/java/com/password4j/BCryptFunction.java index f7f862bb..787e7725 100644 --- a/src/main/java/com/password4j/BCryptFunction.java +++ b/src/main/java/com/password4j/BCryptFunction.java @@ -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)); } } diff --git a/src/main/java/com/password4j/CompressedPBKDF2Function.java b/src/main/java/com/password4j/CompressedPBKDF2Function.java index 4f0a9c30..43377d42 100644 --- a/src/main/java/com/password4j/CompressedPBKDF2Function.java +++ b/src/main/java/com/password4j/CompressedPBKDF2Function.java @@ -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; } @@ -164,7 +164,7 @@ 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 @@ -172,7 +172,7 @@ 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) @@ -180,7 +180,7 @@ 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"); } diff --git a/src/main/java/com/password4j/MessageDigestFunction.java b/src/main/java/com/password4j/MessageDigestFunction.java index b52de249..ebd25dab 100644 --- a/src/main/java/com/password4j/MessageDigestFunction.java +++ b/src/main/java/com/password4j/MessageDigestFunction.java @@ -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) diff --git a/src/main/java/com/password4j/PBKDF2Function.java b/src/main/java/com/password4j/PBKDF2Function.java index ef04e3a8..1967e00a 100644 --- a/src/main/java/com/password4j/PBKDF2Function.java +++ b/src/main/java/com/password4j/PBKDF2Function.java @@ -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) @@ -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 diff --git a/src/main/java/com/password4j/SCryptFunction.java b/src/main/java/com/password4j/SCryptFunction.java index ac58f450..e1460ce3 100644 --- a/src/main/java/com/password4j/SCryptFunction.java +++ b/src/main/java/com/password4j/SCryptFunction.java @@ -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() diff --git a/src/main/java/com/password4j/Utils.java b/src/main/java/com/password4j/Utils.java index 022e6f4a..988fab9e 100644 --- a/src/main/java/com/password4j/Utils.java +++ b/src/main/java/com/password4j/Utils.java @@ -19,13 +19,15 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; -import java.nio.charset.StandardCharsets; +import java.nio.charset.*; import java.util.Arrays; class Utils { - private static final char[] HEX_ALPHABET = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + private static final char[] HEX_ALPHABET = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private Utils() { @@ -34,19 +36,46 @@ private Utils() static byte[] fromCharSequenceToBytes(CharSequence charSequence) { - if (charSequence == null || charSequence.length() == 0) + if (charSequence == null) { return new byte[0]; } - ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(charSequence)); - byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), - byteBuffer.position(), byteBuffer.limit()); + CharsetEncoder encoder = DEFAULT_CHARSET.newEncoder(); + int length = charSequence.length(); + int arraySize = scale(length, encoder.maxBytesPerChar()); + byte[] result = new byte[arraySize]; + if (length == 0) + { + return result; + } + else + { + char[] charArray; + if (charSequence instanceof String) + { + charArray = ((String) charSequence).toCharArray(); + } + else + { + charArray = fromCharSequenceToChars(charSequence); + } + + charArray = Arrays.copyOfRange(charArray, 0, length); + + encoder.onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE).reset(); + + ByteBuffer byteBuffer = ByteBuffer.wrap(result); + CharBuffer charBuffer = CharBuffer.wrap(charArray, 0, length); + + encoder.encode(charBuffer, byteBuffer, true); + encoder.flush(byteBuffer); + + return Arrays.copyOf(result, byteBuffer.position()); + } - // clear sensitive data - Arrays.fill(byteBuffer.array(), (byte) 0); - return bytes; } + static char[] fromCharSequenceToChars(CharSequence charSequence) { if (charSequence == null || charSequence.length() == 0) @@ -58,6 +87,7 @@ static char[] fromCharSequenceToChars(CharSequence charSequence) { result[i] = charSequence.charAt(i); } + return result; } @@ -122,19 +152,20 @@ static byte[] longToLittleEndian(long n) static void longToLittleEndian(long n, byte[] bs, int off) { - intToLittleEndian((int)(n & 0xffffffffL), bs, off); - intToLittleEndian((int)(n >>> 32), bs, off + 4); + intToLittleEndian((int) (n & 0xffffffffL), bs, off); + intToLittleEndian((int) (n >>> 32), bs, off + 4); } static void intToLittleEndian(int n, byte[] bs, int off) { - bs[ off] = (byte)(n ); - bs[++off] = (byte)(n >>> 8); - bs[++off] = (byte)(n >>> 16); - bs[++off] = (byte)(n >>> 24); + bs[off] = (byte) (n); + bs[++off] = (byte) (n >>> 8); + bs[++off] = (byte) (n >>> 16); + bs[++off] = (byte) (n >>> 24); } - public static byte[] intToLittleEndianBytes(int a) { + static byte[] intToLittleEndianBytes(int a) + { byte[] result = new byte[4]; result[0] = (byte) (a & 0xFF); result[1] = (byte) ((a >> 8) & 0xFF); @@ -143,25 +174,30 @@ public static byte[] intToLittleEndianBytes(int a) { return result; } - public static long[] fromBytesToLongs(byte[] input) { + static long[] fromBytesToLongs(byte[] input) + { long[] v = new long[128]; - for (int i = 0; i < v.length; i++) { + for (int i = 0; i < v.length; i++) + { byte[] slice = Arrays.copyOfRange(input, i * 8, (i + 1) * 8); v[i] = littleEndianBytesToLong(slice); } return v; } - public static long littleEndianBytesToLong(byte[] b) { + static long littleEndianBytesToLong(byte[] b) + { long result = 0; - for (int i = 7; i >= 0; i--) { + for (int i = 7; i >= 0; i--) + { result <<= 8; result |= (b[i] & 0xFF); } return result; } - public static byte[] longToLittleEndianBytes(long a) { + static byte[] longToLittleEndianBytes(long a) + { byte[] result = new byte[8]; result[0] = (byte) (a & 0xFF); result[1] = (byte) ((a >> 8) & 0xFF); @@ -174,27 +210,34 @@ public static byte[] longToLittleEndianBytes(long a) { return result; } - public static long intToLong(int x){ + static long intToLong(int x) + { byte[] intBytes = intToLittleEndianBytes(x); byte[] bytes = new byte[8]; System.arraycopy(intBytes, 0, bytes, 0, 4); return littleEndianBytesToLong(bytes); } - public static void xor(long[] t, long[] b1, long[] b2) { - for (int i = 0; i < t.length; i++) { + static void xor(long[] t, long[] b1, long[] b2) + { + for (int i = 0; i < t.length; i++) + { t[i] = b1[i] ^ b2[i]; } } - public static void xor(long[] t, long[] b1, long[] b2, long[] b3) { - for (int i = 0; i < t.length; i++) { + static void xor(long[] t, long[] b1, long[] b2, long[] b3) + { + for (int i = 0; i < t.length; i++) + { t[i] = b1[i] ^ b2[i] ^ b3[i]; } } - public static void xor(long[] t, long[] other) { - for (int i = 0; i < t.length; i++) { + static void xor(long[] t, long[] other) + { + for (int i = 0; i < t.length; i++) + { t[i] = t[i] ^ other[i]; } } @@ -226,4 +269,9 @@ static int log2(int number) return log + (number >>> 1); } + private static int scale(int initialLength, float bytesPerChar) + { + return (int) ((double) initialLength * (double) bytesPerChar); + } + } diff --git a/src/test/com/password4j/Argon2FunctionTest.java b/src/test/com/password4j/Argon2FunctionTest.java index b209cfcd..ca7da0ec 100644 --- a/src/test/com/password4j/Argon2FunctionTest.java +++ b/src/test/com/password4j/Argon2FunctionTest.java @@ -165,6 +165,18 @@ public void parallelTest() throws InterruptedException, ExecutionException } + @Test(expected = BadParametersException.class) + public void badHash() + { + // GIVEN + String invalidHash = "$argon2d$v=19Sm=1024,t=3,p=1$a1hYRFVFUUhMdzF5dk43$GvtgSr24rB/U/idt+1Xq2tn0DIav/H2W0BybTLZijZY"; + + // WHEN + Argon2Function.getInstanceFromHash(invalidHash); + + // TEST + } + private Argon2Function getFunction(int memory, int iterations, int parallelism, int outLength, Argon2 type,int version) { if (version == Argon2Function.ARGON2_VERSION_13) diff --git a/src/test/com/password4j/BCryptFunctionTest.java b/src/test/com/password4j/BCryptFunctionTest.java index 17fe6160..71c90bb6 100644 --- a/src/test/com/password4j/BCryptFunctionTest.java +++ b/src/test/com/password4j/BCryptFunctionTest.java @@ -523,7 +523,7 @@ public void testBadSalt5() String badSalt3 = "$2b$06%ehKGYiS4wt2HAr7KQXS5z."; // WHEN - BCryptFunction.getInstance(10).cryptRaw(password.getBytes(), badSalt3.getBytes(), 6, false, 1); + BCryptFunction.getInstance(10).cryptRaw(password.getBytes(Utils.DEFAULT_CHARSET), badSalt3.getBytes(Utils.DEFAULT_CHARSET), 6, false, 1); } diff --git a/src/test/com/password4j/Blake2bTest.java b/src/test/com/password4j/Blake2bTest.java index 597c48a6..edadc54b 100644 --- a/src/test/com/password4j/Blake2bTest.java +++ b/src/test/com/password4j/Blake2bTest.java @@ -63,7 +63,7 @@ public void test() for (TestCase test : CASES) { Blake2b instance = new Blake2b(test.length); - instance.update(test.message == null ? null : test.message.getBytes()); + instance.update(test.message == null ? null : test.message.getBytes(Utils.DEFAULT_CHARSET)); byte[] out = new byte[test.length]; instance.doFinal(out, 0); assertEquals(test.expected, Utils.toHex(out)); @@ -81,7 +81,7 @@ public void parallelTest() throws InterruptedException, ExecutionException { Callable c = () -> { Blake2b instance = new Blake2b(test.length); - instance.update(test.message == null ? null : test.message.getBytes()); + instance.update(test.message == null ? null : test.message.getBytes(Utils.DEFAULT_CHARSET)); byte[] out = new byte[test.length]; instance.doFinal(out, 0); return test.expected.equals(Utils.toHex(out)); diff --git a/src/test/com/password4j/MessageDigestFunctionTest.java b/src/test/com/password4j/MessageDigestFunctionTest.java index 563679bc..32e6cd8a 100644 --- a/src/test/com/password4j/MessageDigestFunctionTest.java +++ b/src/test/com/password4j/MessageDigestFunctionTest.java @@ -25,6 +25,8 @@ import java.util.Map; import java.util.Set; +import static org.junit.Assert.assertEquals; + public class MessageDigestFunctionTest { @@ -42,7 +44,7 @@ public void testMD5() Hash hash = strategy.hash(password, salt); // THEN - Assert.assertEquals("8223fe8dc0533c6ebbb717e7fda2833c", hash.getResult()); + assertEquals("8223fe8dc0533c6ebbb717e7fda2833c", hash.getResult()); } @@ -57,7 +59,7 @@ public void testMD5noSalt() Hash hash = strategy.hash(password); // THEN - Assert.assertEquals("5f4dcc3b5aa765d61d8327deb882cf99", hash.getResult()); + assertEquals("5f4dcc3b5aa765d61d8327deb882cf99", hash.getResult()); } @Test @@ -113,6 +115,20 @@ public void testMDWrongAlgorithm() // THEN } + @Test + public void testMDWrongSaltOption() + { + // GIVEN + + PropertyReader.properties.setProperty("hash.md.salt.option", "1234"); + + // WHEN + MessageDigestFunction function = AlgorithmFinder.getMessageDigestInstance(); + + // THEN + assertEquals(SaltOption.APPEND, function.getSaltOption()); + } + @Test public void testPBKDF2Check() @@ -172,8 +188,8 @@ public void testAlgorithmFromCode() // THEN Assert.assertNotNull(alg); - Assert.assertEquals(enumAlg.code(), alg.code()); - Assert.assertEquals(enumAlg.bits(), alg.bits()); + assertEquals(enumAlg.code(), alg.code()); + assertEquals(enumAlg.bits(), alg.bits()); } Assert.assertNull(algNull); @@ -230,8 +246,8 @@ public void testPBKDF2equality() // THEN - Assert.assertEquals(4, map.size()); - Assert.assertEquals(strategy1, strategy2); + assertEquals(4, map.size()); + assertEquals(strategy1, strategy2); } @Test @@ -249,9 +265,9 @@ public void testCompressed() Hash notCompressedHash = PBKDF2Function.getInstance(algorithm, 100 * i, algorithm.bits()).hash(password, salt); String params = Long.toString((((long) 100 * i) << 32) | (algorithm.bits() & 0xffffffffL)); - String expected = "$" + algorithm.code() + "$" + params + "$" + Base64.getEncoder().encodeToString(salt.getBytes()) + "$" + notCompressedHash.getResult(); + String expected = "$" + algorithm.code() + "$" + params + "$" + Base64.getEncoder().encodeToString(salt.getBytes(Utils.DEFAULT_CHARSET)) + "$" + notCompressedHash.getResult(); - Assert.assertEquals(expected, hash.getResult()); + assertEquals(expected, hash.getResult()); } } @@ -268,11 +284,11 @@ public void testAccessors() CompressedPBKDF2Function compressed = CompressedPBKDF2Function.getInstance(hmac, iterations, length); // THEN - Assert.assertEquals(hmac.name(), pbkdf2.getAlgorithm()); - Assert.assertEquals(iterations, pbkdf2.getIterations()); - Assert.assertEquals(length, pbkdf2.getLength()); - Assert.assertEquals("PBKDF2Function[SHA384|5|7]", pbkdf2.toString()); - Assert.assertEquals("CompressedPBKDF2Function[SHA384|5|7]", compressed.toString()); + assertEquals(hmac.name(), pbkdf2.getAlgorithm()); + assertEquals(iterations, pbkdf2.getIterations()); + assertEquals(length, pbkdf2.getLength()); + assertEquals("PBKDF2Function[SHA384|5|7]", pbkdf2.toString()); + assertEquals("CompressedPBKDF2Function[SHA384|5|7]", compressed.toString()); } } diff --git a/src/test/com/password4j/PBKDF2FunctionTest.java b/src/test/com/password4j/PBKDF2FunctionTest.java index 082c854b..01aa1568 100644 --- a/src/test/com/password4j/PBKDF2FunctionTest.java +++ b/src/test/com/password4j/PBKDF2FunctionTest.java @@ -16,6 +16,7 @@ */ package com.password4j; +import com.password4j.types.BCrypt; import com.password4j.types.Hmac; import org.junit.Assert; import org.junit.Test; @@ -266,12 +267,46 @@ public void testCompressed() Hash notCompressedHash = PBKDF2Function.getInstance(algorithm, 100 * i, algorithm.bits()).hash(password, salt); String params = Long.toString((((long) 100 * i) << 32) | (algorithm.bits() & 0xffffffffL)); - String expected = "$" + algorithm.code() + "$" + params + "$" + Base64.getEncoder().encodeToString(salt.getBytes()) + "$" + notCompressedHash.getResult(); + String expected = "$" + algorithm.code() + "$" + params + "$" + Base64.getEncoder().encodeToString(salt.getBytes(Utils.DEFAULT_CHARSET)) + "$" + notCompressedHash.getResult(); Assert.assertEquals(expected, hash.getResult()); } } + @Test + public void testEquality() + { + // GIVEN + Hmac hmac = Hmac.SHA256; + int iterations = 2; + int length = 256; + PBKDF2Function pbkdf2Function = PBKDF2Function.getInstance(hmac, iterations, length); + + // THEN + boolean eqNull = pbkdf2Function.equals(null); + boolean eqClass = pbkdf2Function.equals(new BCryptFunction(BCrypt.A,10)); + boolean difInst = pbkdf2Function.equals(SCryptFunction.getInstance(5, 4, 6)); + boolean sameInst = pbkdf2Function.equals(PBKDF2Function.getInstance(hmac, iterations, length)); + boolean notSameInst1 = pbkdf2Function.equals(PBKDF2Function.getInstance(Hmac.SHA1, iterations, length)); + boolean notSameInst2 = pbkdf2Function.equals(PBKDF2Function.getInstance(hmac, iterations+1, length)); + boolean notSameInst3 = pbkdf2Function.equals(PBKDF2Function.getInstance(hmac, iterations, length*2)); + + String toString = pbkdf2Function.toString(); + int hashCode = pbkdf2Function.hashCode(); + + // END + Assert.assertFalse(eqNull); + Assert.assertFalse(eqClass); + Assert.assertFalse(difInst); + Assert.assertTrue(sameInst); + Assert.assertNotEquals(toString, new SCryptFunction(5, 4, 6).toString()); + Assert.assertNotEquals(hashCode, new SCryptFunction(5, 4, 6).hashCode()); + Assert.assertFalse(notSameInst1); + Assert.assertFalse(notSameInst2); + Assert.assertFalse(notSameInst3); + + } + @Test public void testAccessors() { diff --git a/src/test/com/password4j/SCryptFunctionTest.java b/src/test/com/password4j/SCryptFunctionTest.java index aaf5b536..ec028a52 100644 --- a/src/test/com/password4j/SCryptFunctionTest.java +++ b/src/test/com/password4j/SCryptFunctionTest.java @@ -119,6 +119,9 @@ public void testEquality() boolean sameInst = scrypt.equals(SCryptFunction.getInstance(N, r, p)); String toString = scrypt.toString(); int hashCode = scrypt.hashCode(); + boolean notSameInst1 = scrypt.equals(SCryptFunction.getInstance(r+1, N, p)); + boolean notSameInst2 = scrypt.equals(SCryptFunction.getInstance(r, N+1+1, p)); + boolean notSameInst3 = scrypt.equals(SCryptFunction.getInstance(r, N, p+1)); // END Assert.assertFalse(eqNull); @@ -127,6 +130,9 @@ public void testEquality() Assert.assertTrue(sameInst); Assert.assertNotEquals(toString, new SCryptFunction(5, 4, 6).toString()); Assert.assertNotEquals(hashCode, new SCryptFunction(5, 4, 6).hashCode()); + Assert.assertFalse(notSameInst1); + Assert.assertFalse(notSameInst2); + Assert.assertFalse(notSameInst3); } @Test diff --git a/src/test/com/password4j/SaltGeneratorTest.java b/src/test/com/password4j/SaltGeneratorTest.java index 041a7203..22e7913e 100644 --- a/src/test/com/password4j/SaltGeneratorTest.java +++ b/src/test/com/password4j/SaltGeneratorTest.java @@ -1,12 +1,25 @@ package com.password4j; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import java.security.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + public class SaltGeneratorTest { + @Before + public void init() + { + PropertyReader.properties.setProperty("global.random.strong", "false"); + AlgorithmFinder.initialize(); + } + @Test public void testSaltLength() { @@ -41,7 +54,7 @@ public void testSaltNegativeLength() // GIVEN // WHEN - byte[] salt = SaltGenerator.generate(-3); + SaltGenerator.generate(-3); // THEN @@ -61,5 +74,72 @@ public void testSaltZeroLength() Assert.assertEquals(length, salt.length); } + @Test + public void testStrongRandom() + { + // GIVEN + + PropertyReader.properties.setProperty("global.random.strong", "true"); + + // WHEN + AlgorithmFinder.initialize(); + + // THEN + + try + { + assertEquals(SecureRandom.getInstanceStrong().getAlgorithm(), AlgorithmFinder.getSecureRandom().getAlgorithm()); + + } + catch (NoSuchAlgorithmException e) + { + // + } + + } + + @Test + public void testStrongRandom2() + { + // GIVEN + PropertyReader.properties.setProperty("global.random.strong", "true"); + + // WHEN + + String old = getSecurityProperty(); + String strongAlg; + try + { + strongAlg = SecureRandom.getInstanceStrong().getAlgorithm(); + } + catch (Exception e) + { + return; + } + + setSecurityProperty("not and algorithm"); + AlgorithmFinder.initialize(); + + // THEN + + + assertNotEquals(strongAlg, AlgorithmFinder.getSecureRandom().getAlgorithm()); + setSecurityProperty(old); + + } + + private String getSecurityProperty() + { + return AccessController.doPrivileged((PrivilegedAction) () -> Security.getProperty("securerandom.strongAlgorithms")); + } + + private void setSecurityProperty(String value) + { + AccessController.doPrivileged((PrivilegedAction) () -> { + Security.setProperty("securerandom.strongAlgorithms", value); + return null; + }); + } + } diff --git a/src/test/com/password4j/StringTest.java b/src/test/com/password4j/StringTest.java index 4bd3c4b4..8b436199 100644 --- a/src/test/com/password4j/StringTest.java +++ b/src/test/com/password4j/StringTest.java @@ -162,6 +162,7 @@ public void testUtilities() char[] c2 = Utils.fromCharSequenceToChars(new String(new char[0])); byte[] b1 = Utils.fromCharSequenceToBytes(null); byte[] b2 = Utils.fromCharSequenceToBytes(new String(new char[0])); + byte[] b3 = Utils.fromCharSequenceToBytes(new String(new char[]{(char)1})); CharSequence cs1 = Utils.append("a", null); CharSequence cs2 = Utils.append(null, "b"); @@ -174,6 +175,8 @@ public void testUtilities() Assert.assertEquals(Arrays.toString(c1), Arrays.toString(c2)); Assert.assertEquals(Arrays.toString(b1), Arrays.toString(b2)); + Assert.assertEquals(1, b3.length); + Assert.assertEquals(1, b3[0]); Assert.assertEquals("a", cs1); Assert.assertEquals("b", cs2); Assert.assertEquals(Arrays.toString(new char[] { 'a', 'b', 'c', 'd', 'e', 'f' }),