Skip to content

Commit

Permalink
Add tests + @author tag
Browse files Browse the repository at this point in the history
  • Loading branch information
dreezey committed Dec 9, 2019
1 parent 75ab47f commit 96c6be0
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 62 deletions.
15 changes: 4 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.6.0.1398</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
Expand Down Expand Up @@ -108,17 +103,15 @@
<artifactId>keycloak-services</artifactId>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<!-- <dependency>-->
<!-- <groupId>javax.validation</groupId>-->
<!-- <artifactId>validation-api</artifactId>-->
<!-- <version>2.0.1.Final</version>-->
<!-- </dependency>-->
<dependency>
<groupId>de.mkammerer</groupId>
<artifactId>argon2-jvm</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,6 +11,9 @@

import java.security.SecureRandom;

/**
* @author <a href="mailto:[email protected]">Dries Eestermans</a>
*/
public class Argon2PasswordHashProvider implements PasswordHashProvider {

private static final Logger LOG = Logger.getLogger(Argon2PasswordHashProvider.class);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;

/**
* @author <a href="mailto:[email protected]">Dries Eestermans</a>
*/
public class Argon2PasswordHashProviderFactory implements PasswordHashProviderFactory {
private static final Logger LOG = Logger.getLogger(Argon2PasswordHashProviderFactory.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import org.keycloak.policy.PasswordPolicyProviderFactory;
import org.keycloak.policy.PolicyError;

/**
* @author <a href="mailto:[email protected]">Dries Eestermans</a>
*/
public class Argon2HashLengthPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
public static final String ID = "argon2HashLength";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import org.keycloak.policy.PasswordPolicyProviderFactory;
import org.keycloak.policy.PolicyError;

/**
* @author <a href="mailto:[email protected]">Dries Eestermans</a>
*/
public class Argon2IterationsPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
public static final String ID = "argon2Iterations";

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,6 +9,9 @@
import org.keycloak.policy.PasswordPolicyProviderFactory;
import org.keycloak.policy.PolicyError;

/**
* @author <a href="mailto:[email protected]">Dries Eestermans</a>
*/
public class Argon2MaxTimePasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
public static final String ID = "argon2MaxTime";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import org.keycloak.policy.PasswordPolicyProviderFactory;
import org.keycloak.policy.PolicyError;

/**
* @author <a href="mailto:[email protected]">Dries Eestermans</a>
*/
public class Argon2MemoryPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
public static final String ID = "argon2Memory";
private final int DEFAULT_ARGON2_MEMORY = 65536;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import org.keycloak.policy.PasswordPolicyProviderFactory;
import org.keycloak.policy.PolicyError;

/**
* @author <a href="mailto:[email protected]">Dries Eestermans</a>
*/
public class Argon2ParallelismPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
public static final String ID = "argon2Parallelism";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import org.keycloak.policy.PasswordPolicyProviderFactory;
import org.keycloak.policy.PolicyError;

/**
* @author <a href="mailto:[email protected]">Dries Eestermans</a>
*/
public class Argon2SaltLengthPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
public static final String ID = "argon2SaltLength";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import org.keycloak.policy.PasswordPolicyProviderFactory;
import org.keycloak.policy.PolicyError;

/**
* @author <a href="mailto:[email protected]">Dries Eestermans</a>
*/
public class Argon2VariantPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
public static final String ID = "argon2Variant";
private final String DEFAULT_ARGON2_VARIANT = "ARGON2id";
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/be/cronos/keycloak/utils/Argon2Helper.java
Original file line number Diff line number Diff line change
@@ -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 <a href="mailto:[email protected]">Dries Eestermans</a>
*/
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;
}
}
Loading

0 comments on commit 96c6be0

Please sign in to comment.