-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
353 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
@@ -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); | ||
|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
@@ -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"; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.