diff --git a/pom.xml b/pom.xml index 5701693..df44ed0 100644 --- a/pom.xml +++ b/pom.xml @@ -29,11 +29,6 @@ 1.8 - - org.sonarsource.scanner.maven - sonar-maven-plugin - 3.6.0.1398 - maven-dependency-plugin @@ -108,17 +103,15 @@ keycloak-services provided - - - - - - de.mkammerer argon2-jvm 2.6 + + junit + junit + diff --git a/src/main/java/be/cronos/keycloak/credential/hash/Argon2PasswordHashProvider.java b/src/main/java/be/cronos/keycloak/credential/hash/Argon2PasswordHashProvider.java index 1e22839..624988e 100644 --- a/src/main/java/be/cronos/keycloak/credential/hash/Argon2PasswordHashProvider.java +++ b/src/main/java/be/cronos/keycloak/credential/hash/Argon2PasswordHashProvider.java @@ -1,8 +1,7 @@ package be.cronos.keycloak.credential.hash; import be.cronos.keycloak.policy.*; -import de.mkammerer.argon2.Argon2; -import de.mkammerer.argon2.Argon2Factory; +import be.cronos.keycloak.utils.Argon2Helper; import de.mkammerer.argon2.Argon2Factory.Argon2Types; import org.jboss.logging.Logger; import org.keycloak.credential.hash.PasswordHashProvider; @@ -12,6 +11,9 @@ import java.security.SecureRandom; +/** + * @author Dries Eestermans + */ public class Argon2PasswordHashProvider implements PasswordHashProvider { private static final Logger LOG = Logger.getLogger(Argon2PasswordHashProvider.class); @@ -70,23 +72,16 @@ public PasswordCredentialModel encodedCredential(String rawPassword, int iterati LOG.debugf("\tSalt Length: %d", saltLength); LOG.debugf("\tMaximum time for hashing (in ms): %d", maxTime); - Argon2 argon2 = Argon2Factory.createAdvanced(argon2Variant, defaultSaltLength, defaultHashLength); - String hash; + // Keep track of hashing runtime + long start = System.currentTimeMillis(); + String hash = Argon2Helper.hashPassword(rawPassword, argon2Variant, argon2Iterations, parallelism, memoryLimit, hashLength, saltLength); + // Stop timing + long end = System.currentTimeMillis(); - try { - // Keep track of hashing runtime - long start = System.currentTimeMillis(); - // Hash the password - hash = argon2.hash(argon2Iterations, memoryLimit, parallelism, rawPassword.getBytes()); - // Stop timing - long end = System.currentTimeMillis(); - // Verify whether the hash time has not exceeded the configured value (or default value) - LOG.debugf("Hashing runtime was %d milliseconds (%d seconds).", end-start, (end-start)/1000); - if (end - start > maxTime) { - LOG.warnf("Hash time exceeded configured maximum time: '%d ms', consider tuning the parameter 'Argon2 Iterations'.", maxTime); - } - } catch (Exception e) { - throw new RuntimeException(e); + // Verify whether the hash time has not exceeded the configured value (or default value) + LOG.debugf("Hashing runtime was %d milliseconds (%d seconds).", end-start, (end-start)/1000); + if (end - start > maxTime) { + LOG.warnf("Hash time exceeded configured maximum time: '%d ms', consider tuning the parameter 'Argon2 Iterations'.", maxTime); } // Salt doesn't matter here @@ -120,38 +115,7 @@ public boolean verify(String rawPassword, PasswordCredentialModel credential) { LOG.debugf("verify()"); - // Get the Argon2 variant of the credential, should be something like: - // $argon2i$v=19$m=65535,t=30,p=4$JQUxqirAz7+Em0yM1ZiDFA$LhqtL0XPGESfeHb4lI2XnV4mSZacWGQWANKtvIVVpy4 - // however, the variant's case is not correct for the enum - String storedVariant = credential.getPasswordSecretData().getValue().split("\\$")[1]; - Argon2Types storedArgon2Variant = null; - try { - for (Argon2Types argon2Type : Argon2Types.values()) { - if (argon2Type.toString().equalsIgnoreCase(storedVariant)) { - storedArgon2Variant = argon2Type; - LOG.debugf("Stored variant found: %s", storedVariant); - } - } - if (storedArgon2Variant == null) throw new Exception("Unknown stored Argon2 variant"); - } catch (Exception e) { - throw new RuntimeException("Unknown stored Argon2 variant, is someone spoofing?"); - } - - // Now make sure to select the correct variant for the Argon2Factory - Argon2 argon2 = Argon2Factory.createAdvanced(storedArgon2Variant); - - boolean samePassword = false; - try { - if (argon2.verify(credential.getPasswordSecretData().getValue(), rawPassword.getBytes())) { - LOG.debugf("Passwords match!!"); - samePassword = true; - } else { - LOG.debugf("Passwords don't match!!"); - } - } catch (Exception e) { - LOG.debugf("Couldn't compare password, exception occurred: %s", e.getMessage()); - } - return samePassword; + return Argon2Helper.verifyPassword(rawPassword, credential); } @Override diff --git a/src/main/java/be/cronos/keycloak/credential/hash/Argon2PasswordHashProviderFactory.java b/src/main/java/be/cronos/keycloak/credential/hash/Argon2PasswordHashProviderFactory.java index 73d6484..b8e92e5 100644 --- a/src/main/java/be/cronos/keycloak/credential/hash/Argon2PasswordHashProviderFactory.java +++ b/src/main/java/be/cronos/keycloak/credential/hash/Argon2PasswordHashProviderFactory.java @@ -9,6 +9,9 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +/** + * @author Dries Eestermans + */ public class Argon2PasswordHashProviderFactory implements PasswordHashProviderFactory { private static final Logger LOG = Logger.getLogger(Argon2PasswordHashProviderFactory.class); diff --git a/src/main/java/be/cronos/keycloak/policy/Argon2HashLengthPasswordPolicyProviderFactory.java b/src/main/java/be/cronos/keycloak/policy/Argon2HashLengthPasswordPolicyProviderFactory.java index 9125c1c..d8bc75b 100644 --- a/src/main/java/be/cronos/keycloak/policy/Argon2HashLengthPasswordPolicyProviderFactory.java +++ b/src/main/java/be/cronos/keycloak/policy/Argon2HashLengthPasswordPolicyProviderFactory.java @@ -10,6 +10,9 @@ import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.policy.PolicyError; +/** + * @author Dries Eestermans + */ public class Argon2HashLengthPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory { public static final String ID = "argon2HashLength"; diff --git a/src/main/java/be/cronos/keycloak/policy/Argon2IterationsPasswordPolicyProviderFactory.java b/src/main/java/be/cronos/keycloak/policy/Argon2IterationsPasswordPolicyProviderFactory.java index 958bc3e..a8e749c 100644 --- a/src/main/java/be/cronos/keycloak/policy/Argon2IterationsPasswordPolicyProviderFactory.java +++ b/src/main/java/be/cronos/keycloak/policy/Argon2IterationsPasswordPolicyProviderFactory.java @@ -9,6 +9,9 @@ import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.policy.PolicyError; +/** + * @author Dries Eestermans + */ public class Argon2IterationsPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory { public static final String ID = "argon2Iterations"; diff --git a/src/main/java/be/cronos/keycloak/policy/Argon2MaxTimePasswordPolicyProviderFactory.java b/src/main/java/be/cronos/keycloak/policy/Argon2MaxTimePasswordPolicyProviderFactory.java index 048c76e..3089161 100644 --- a/src/main/java/be/cronos/keycloak/policy/Argon2MaxTimePasswordPolicyProviderFactory.java +++ b/src/main/java/be/cronos/keycloak/policy/Argon2MaxTimePasswordPolicyProviderFactory.java @@ -1,6 +1,5 @@ package be.cronos.keycloak.policy; -import de.mkammerer.argon2.Argon2Constants; import org.keycloak.Config; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -10,6 +9,9 @@ import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.policy.PolicyError; +/** + * @author Dries Eestermans + */ public class Argon2MaxTimePasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory { public static final String ID = "argon2MaxTime"; diff --git a/src/main/java/be/cronos/keycloak/policy/Argon2MemoryPasswordPolicyProviderFactory.java b/src/main/java/be/cronos/keycloak/policy/Argon2MemoryPasswordPolicyProviderFactory.java index 4c90b3d..875ac68 100644 --- a/src/main/java/be/cronos/keycloak/policy/Argon2MemoryPasswordPolicyProviderFactory.java +++ b/src/main/java/be/cronos/keycloak/policy/Argon2MemoryPasswordPolicyProviderFactory.java @@ -9,6 +9,9 @@ import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.policy.PolicyError; +/** + * @author Dries Eestermans + */ public class Argon2MemoryPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory { public static final String ID = "argon2Memory"; private final int DEFAULT_ARGON2_MEMORY = 65536; diff --git a/src/main/java/be/cronos/keycloak/policy/Argon2ParallelismPasswordPolicyProviderFactory.java b/src/main/java/be/cronos/keycloak/policy/Argon2ParallelismPasswordPolicyProviderFactory.java index 497ef33..b3053cb 100644 --- a/src/main/java/be/cronos/keycloak/policy/Argon2ParallelismPasswordPolicyProviderFactory.java +++ b/src/main/java/be/cronos/keycloak/policy/Argon2ParallelismPasswordPolicyProviderFactory.java @@ -6,6 +6,9 @@ import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.policy.PolicyError; +/** + * @author Dries Eestermans + */ public class Argon2ParallelismPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory { public static final String ID = "argon2Parallelism"; diff --git a/src/main/java/be/cronos/keycloak/policy/Argon2SaltLengthPasswordPolicyProviderFactory.java b/src/main/java/be/cronos/keycloak/policy/Argon2SaltLengthPasswordPolicyProviderFactory.java index b1657e9..e8e7825 100644 --- a/src/main/java/be/cronos/keycloak/policy/Argon2SaltLengthPasswordPolicyProviderFactory.java +++ b/src/main/java/be/cronos/keycloak/policy/Argon2SaltLengthPasswordPolicyProviderFactory.java @@ -10,6 +10,9 @@ import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.policy.PolicyError; +/** + * @author Dries Eestermans + */ public class Argon2SaltLengthPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory { public static final String ID = "argon2SaltLength"; diff --git a/src/main/java/be/cronos/keycloak/policy/Argon2VariantPasswordPolicyProviderFactory.java b/src/main/java/be/cronos/keycloak/policy/Argon2VariantPasswordPolicyProviderFactory.java index 7ceab87..df4a8f3 100644 --- a/src/main/java/be/cronos/keycloak/policy/Argon2VariantPasswordPolicyProviderFactory.java +++ b/src/main/java/be/cronos/keycloak/policy/Argon2VariantPasswordPolicyProviderFactory.java @@ -8,6 +8,9 @@ import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.policy.PolicyError; +/** + * @author Dries Eestermans + */ public class Argon2VariantPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory { public static final String ID = "argon2Variant"; private final String DEFAULT_ARGON2_VARIANT = "ARGON2id"; diff --git a/src/main/java/be/cronos/keycloak/utils/Argon2Helper.java b/src/main/java/be/cronos/keycloak/utils/Argon2Helper.java new file mode 100644 index 0000000..b05a6d4 --- /dev/null +++ b/src/main/java/be/cronos/keycloak/utils/Argon2Helper.java @@ -0,0 +1,64 @@ +package be.cronos.keycloak.utils; + +import de.mkammerer.argon2.Argon2; +import de.mkammerer.argon2.Argon2Factory; +import org.jboss.logging.Logger; +import org.keycloak.models.credential.PasswordCredentialModel; + +/** + * @author Dries Eestermans + */ +public class Argon2Helper { + private static final Logger LOG = Logger.getLogger(Argon2Helper.class); + + public static String hashPassword(String rawPassword, Argon2Factory.Argon2Types argon2Variant, int iterations, + int parallelism, int memoryLimit, int hashLength, int saltLength) { + if (rawPassword == null) throw new RuntimeException("Password can't be empty"); + Argon2 argon2 = Argon2Factory.createAdvanced(argon2Variant, saltLength, hashLength); + String hash; + + try { + // Hash the password + hash = argon2.hash(iterations, memoryLimit, parallelism, rawPassword.toCharArray()); + } catch (Exception e) { + throw new RuntimeException(e); + } + return hash; + } + + public static boolean verifyPassword(String rawPassword, + PasswordCredentialModel credential) { + // Get the Argon2 variant of the credential, should be something like: + // $argon2i$v=19$m=65535,t=30,p=4$JQUxqirAz7+Em0yM1ZiDFA$LhqtL0XPGESfeHb4lI2XnV4mSZacWGQWANKtvIVVpy4 + // however, the variant's case is not correct for the enum + String storedVariant = credential.getPasswordSecretData().getValue().split("\\$")[1]; + Argon2Factory.Argon2Types storedArgon2Variant = null; + try { + for (Argon2Factory.Argon2Types argon2Type : Argon2Factory.Argon2Types.values()) { + if (argon2Type.toString().equalsIgnoreCase(storedVariant)) { + storedArgon2Variant = argon2Type; + LOG.debugf("Stored variant found: %s", storedVariant); + } + } + if (storedArgon2Variant == null) throw new Exception("Unknown stored Argon2 variant"); + } catch (Exception e) { + throw new RuntimeException("Unknown stored Argon2 variant, is someone spoofing?"); + } + + // Now make sure to select the correct variant for the Argon2Factory + Argon2 argon2 = Argon2Factory.createAdvanced(storedArgon2Variant); + + boolean samePassword = false; + try { + if (argon2.verify(credential.getPasswordSecretData().getValue(), rawPassword.toCharArray())) { + LOG.debugf("Passwords match!!"); + samePassword = true; + } else { + LOG.debugf("Passwords don't match!!"); + } + } catch (Exception e) { + LOG.debugf("Couldn't compare password, exception occurred: %s", e.getMessage()); + } + return samePassword; + } +} diff --git a/src/test/java/be/cronos/keycloak/utils/Argon2HelperTest.java b/src/test/java/be/cronos/keycloak/utils/Argon2HelperTest.java new file mode 100644 index 0000000..092cff2 --- /dev/null +++ b/src/test/java/be/cronos/keycloak/utils/Argon2HelperTest.java @@ -0,0 +1,247 @@ +package be.cronos.keycloak.utils; + +import be.cronos.keycloak.credential.hash.Argon2PasswordHashProviderFactory; +import de.mkammerer.argon2.Argon2Constants; +import de.mkammerer.argon2.Argon2Factory; +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.models.credential.PasswordCredentialModel; + +/** + * @author Dries Eestermans + */ +public class Argon2HelperTest { + private static final String ALGORITHM = Argon2PasswordHashProviderFactory.ID; + private static final int DEFAULT_ITERATIONS = 1; + + private static final int DEFAULT_MEMORY = 65536; + + private static final int DEFAULT_PARALLELISM = 1; + + private static final int DEFAULT_MAX_TIME = 1000; + + // region: argon2d + @Test + public void testArgon2dHashAndVerifySamePassword() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2d; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2d"; + String hash = Argon2Helper.hashPassword(rawPassword, argon2Variant, iterations, DEFAULT_PARALLELISM, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertTrue(verified); + } + + @Test + public void testArgon2dHashAndVerifyDifferentPassword() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2d; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2d"; + String hash = Argon2Helper.hashPassword(rawPassword, argon2Variant, iterations, DEFAULT_PARALLELISM, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword("different", passwordCredentialModel); + Assert.assertFalse(verified); + } + + @Test + public void testArgon2dVerifyPredefinedHash() { + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2d"; + String hash = "$argon2d$v=19$m=65536,t=1,p=1$v3evK1HhIHKHRnRNWqEfZA$T7G+ujnDpZN+kYuMngOb/2+/mIDpOn0VyLIh7B6LJiY"; + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertTrue(verified); + } + + @Test + public void testArgon2dVerifyPredefinedWrongHash() { + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "wrongpassword"; + String hash = "$argon2d$v=19$m=65536,t=1,p=1$v3evK1HhIHKHRnRNWqEfZA$T7G+ujnDpZN+kYuMngOb/2+/mIDpOn0VyLIh7B6LJiY"; + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertFalse(verified); + } + + // endregion: argon2d + + // region: argon2i + @Test + public void testArgon2iHashAndVerifySamePassword() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2i; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2i"; + String hash = Argon2Helper.hashPassword(rawPassword, argon2Variant, iterations, DEFAULT_PARALLELISM, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertTrue(verified); + } + + @Test + public void testArgon2iHashAndVerifyDifferentPassword() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2i; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2i"; + String hash = Argon2Helper.hashPassword(rawPassword, argon2Variant, iterations, DEFAULT_PARALLELISM, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword("different", passwordCredentialModel); + Assert.assertFalse(verified); + } + + @Test + public void testArgon2iVerifyPredefinedHash() { + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2i"; + String hash = "$argon2i$v=19$m=65536,t=1,p=1$81E/xOo/2OUX15UAJgI3Eg$0Z83Ag5oE9MCEEVGL9NJNg6oFIVbU/FhpQkyyX+RNz0"; + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertTrue(verified); + } + + @Test + public void testArgon2iVerifyPredefinedWrongHash() { + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "wrongpassword"; + String hash = "$argon2i$v=19$m=65536,t=1,p=1$81E/xOo/2OUX15UAJgI3Eg$0Z83Ag5oE9MCEEVGL9NJNg6oFIVbU/FhpQkyyX+RNz0"; + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertFalse(verified); + } + // endregion: argon2i + + // region: argon2id + @Test + public void testArgon2idHashAndVerifySamePassword() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2id; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2id"; + String hash = Argon2Helper.hashPassword(rawPassword, argon2Variant, iterations, DEFAULT_PARALLELISM, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertTrue(verified); + } + + @Test + public void testArgon2idHashAndVerifyDifferentPassword() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2id; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2id"; + String hash = Argon2Helper.hashPassword(rawPassword, argon2Variant, iterations, DEFAULT_PARALLELISM, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword("different", passwordCredentialModel); + Assert.assertFalse(verified); + } + + @Test + public void testArgon2idVerifyPredefinedHash() { + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2id"; + String hash = "$argon2id$v=19$m=65536,t=1,p=1$zGFM95kyhWZyZv1Hhvjuog$G78Vd4nXEqN0DKbF+qGj1pUNyEpEZmOWqEqlHFDllJY"; + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertTrue(verified); + } + + @Test + public void testArgon2idVerifyPredefinedWrongHash() { + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "wrongpassword"; + String hash = "$argon2i$v=19$m=65536,t=1,p=1$81E/xOo/2OUX15UAJgI3Eg$0Z83Ag5oE9MCEEVGL9NJNg6oFIVbU/FhpQkyyX+RNz0"; + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertFalse(verified); + } + // endregion: argon2id + + // region: runtime exceptions + @Test(expected = RuntimeException.class) + public void testHashPasswordHashEmptyPassword() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2id; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = null; + String hash = Argon2Helper.hashPassword(rawPassword, argon2Variant, iterations, DEFAULT_PARALLELISM, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + } + + @Test(expected = RuntimeException.class) + public void testHashPasswordNoAlgorithm() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2id; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "novariantdefined"; + String hash = Argon2Helper.hashPassword(rawPassword, null, iterations, DEFAULT_PARALLELISM, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + } + + // Keeps on processing +// @Test(expected = RuntimeException.class) +// public void testHashPasswordNegativeIterations() { +// Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2id; +// int iterations = -1; +// String rawPassword = "novariantdefined"; +// String hash = Argon2Helper.hashPassword(rawPassword, argon2Variant, iterations, DEFAULT_PARALLELISM, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); +// } + + @Test(expected = RuntimeException.class) + public void testHashPasswordNoParallelism() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2id; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "novariantdefined"; + String hash = Argon2Helper.hashPassword(rawPassword, null, iterations, 0, DEFAULT_MEMORY, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + } + + @Test(expected = RuntimeException.class) + public void testHashPasswordNoMemory() { + Argon2Factory.Argon2Types argon2Variant = Argon2Factory.Argon2Types.ARGON2id; + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "novariantdefined"; + String hash = Argon2Helper.hashPassword(rawPassword, null, iterations, DEFAULT_PARALLELISM, 0, Argon2Constants.DEFAULT_HASH_LENGTH, Argon2Constants.DEFAULT_SALT_LENGTH); + } + + @Test(expected = RuntimeException.class) + public void testVerifyPasswordInvalidAlgorithm() { + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2id"; + String hash = "$argon2idd$v=19$m=65536,t=1,p=1$zGFM95kyhWZyZv1Hhvjuog$G78Vd4nXEqN0DKbF+qGj1pUNyEpEZmOWqEqlHFDllJY"; + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + } + + @Test(expected = RuntimeException.class) + public void testVerifyPasswordNonsenseData() { + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2id"; + String hash = "nonsense"; + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + } + // endregion: runtime exceptions + + // region: wrong algorithm in hash + + @Test() + public void testVerifyPasswordIncorrectAlgorithm() { + int iterations = DEFAULT_ITERATIONS; + String rawPassword = "testargon2id"; + // it should argon2id + String hash = "$argon2i$v=19$m=65536,t=1,p=1$zGFM95kyhWZyZv1Hhvjuog$G78Vd4nXEqN0DKbF+qGj1pUNyEpEZmOWqEqlHFDllJY"; + PasswordCredentialModel passwordCredentialModel = PasswordCredentialModel.createFromValues(ALGORITHM, "".getBytes(), iterations, hash); + passwordCredentialModel.setSecretData(hash); + boolean verified = Argon2Helper.verifyPassword(rawPassword, passwordCredentialModel); + Assert.assertFalse(verified); + } + + // endregion: wrong algorithm in hash + +}