keycloak-uncached

KEYCLOAK-1900 Refactoring

12/8/2015 10:25:50 AM

Changes

model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProvider.java 41(+0 -41)

model/api/src/main/java/org/keycloak/hash/DefaultPasswordHashProviderFactory.java 35(+0 -35)

model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java 106(+0 -106)

Details

diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index 4365291..7b94680 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -1103,6 +1103,7 @@ module.factory('PasswordPolicy', function() {
     var p = {};
 
     p.policyMessages = {
+        hashAlgorithm: 	"Default hashing algorithm.  Default is 'pbkdf2'.",
         hashIterations: 	"Number of hashing iterations.  Default is 1.  Recommended is 50000.",
         length:         	"Minimal password length (integer type). Default value is 8.",
         digits:         	"Minimal number (integer type) of digits in password. Default value is 1.",
@@ -1116,6 +1117,7 @@ module.factory('PasswordPolicy', function() {
     }
 
     p.allPolicies = [
+        { name: 'hashAlgorithm', value: 'pbkdf2' },
         { name: 'hashIterations', value: 1 },
         { name: 'length', value: 8 },
         { name: 'digits', value: 1 },
diff --git a/model/api/src/main/java/org/keycloak/hash/PasswordHashManager.java b/model/api/src/main/java/org/keycloak/hash/PasswordHashManager.java
new file mode 100644
index 0000000..5988a41
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/hash/PasswordHashManager.java
@@ -0,0 +1,30 @@
+package org.keycloak.hash;
+
+import org.keycloak.models.*;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordHashManager {
+
+    public static UserCredentialValueModel encode(KeycloakSession session, RealmModel realm, String rawPassword) {
+        PasswordPolicy passwordPolicy = realm.getPasswordPolicy();
+        String algorithm = passwordPolicy.getHashAlgorithm();
+        int iterations = passwordPolicy.getHashIterations();
+        if (iterations < 1) {
+            iterations = 1;
+        }
+        PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm);
+        if (provider == null) {
+            throw new RuntimeException("Password hash provider for algorithm " + algorithm + " not found");
+        }
+        return provider.encode(rawPassword, iterations);
+    }
+
+    public static boolean verify(KeycloakSession session, RealmModel realm, String password, UserCredentialValueModel credential) {
+        String algorithm = credential.getAlgorithm() != null ? credential.getAlgorithm() : realm.getPasswordPolicy().getHashAlgorithm();
+        PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm);
+        return provider.verify(password, credential);
+    }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java b/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java
index 34dc14a..df0e21d 100644
--- a/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java
+++ b/model/api/src/main/java/org/keycloak/hash/PasswordHashProvider.java
@@ -1,5 +1,6 @@
 package org.keycloak.hash;
 
+import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.provider.Provider;
 
 /**
@@ -7,10 +8,8 @@ import org.keycloak.provider.Provider;
  */
 public interface PasswordHashProvider extends Provider {
 
-    String encode(String rawPassword, byte[] salt);
+    UserCredentialValueModel encode(String rawPassword, int iterations);
 
-    String encode(String rawPassword, byte[] salt, int iterations);
-
-    boolean verify(String rawPassword, String encodedPassword, byte[] salt);
+    boolean verify(String rawPassword, UserCredentialValueModel credential);
 
 }
diff --git a/model/api/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java b/model/api/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java
new file mode 100644
index 0000000..2b2c9b7
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java
@@ -0,0 +1,91 @@
+package org.keycloak.hash;
+
+import org.keycloak.Config;
+import org.keycloak.common.util.Base64;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+/**
+ * @author <a href="mailto:me@tsudot.com">Kunal Kerkar</a>
+ */
+public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, PasswordHashProvider {
+
+    public static final String ID = "pbkdf2";
+
+    private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
+    private static final int DERIVED_KEY_SIZE = 512;
+
+    public UserCredentialValueModel encode(String rawPassword, int iterations) {
+        byte[] salt = getSalt();
+        String encodedPassword = encode(rawPassword, iterations, salt);
+
+        UserCredentialValueModel credentials = new UserCredentialValueModel();
+        credentials.setAlgorithm(ID);
+        credentials.setType(UserCredentialModel.PASSWORD);
+        credentials.setSalt(salt);
+        credentials.setHashIterations(iterations);
+        credentials.setValue(encodedPassword);
+        return credentials;
+    }
+
+    public boolean verify(String rawPassword, UserCredentialValueModel credential) {
+        return encode(rawPassword, credential.getHashIterations(), credential.getSalt()).equals(credential.getValue());
+    }
+
+    @Override
+    public PasswordHashProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    private String encode(String rawPassword, int iterations, byte[] salt) {
+        KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, DERIVED_KEY_SIZE);
+
+        try {
+            byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();
+            return Base64.encodeBytes(key);
+        } catch (InvalidKeySpecException e) {
+            throw new RuntimeException("Credential could not be encoded");
+        }
+    }
+
+    private byte[] getSalt() {
+        byte[] buffer = new byte[16];
+        SecureRandom secureRandom = new SecureRandom();
+        secureRandom.nextBytes(buffer);
+        return buffer;
+    }
+
+    private SecretKeyFactory getSecretKeyFactory() {
+        try {
+            return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("PBKDF2 algorithm not found");
+        }
+    }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
index 3674334..b7ed682 100755
--- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
+++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -1,6 +1,7 @@
 package org.keycloak.models;
 
-import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
+import org.keycloak.hash.PasswordHashManager;
+import org.keycloak.hash.PasswordHashProvider;
 
 import java.io.Serializable;
 import java.util.ArrayList;
@@ -67,6 +68,8 @@ public class PasswordPolicy implements Serializable {
                 list.add(new SpecialChars(arg));
             } else if (name.equals(NotUsername.NAME)) {
                 list.add(new NotUsername(arg));
+            } else if (name.equals(HashAlgorithm.NAME)) {
+                list.add(new HashAlgorithm(arg));
             } else if (name.equals(HashIterations.NAME)) {
                 list.add(new HashIterations(arg));
             } else if (name.equals(RegexPatterns.NAME)) {
@@ -83,6 +86,18 @@ public class PasswordPolicy implements Serializable {
         return list;
     }
 
+    public String getHashAlgorithm() {
+        if (policies == null)
+            return Constants.DEFAULT_HASH_ALGORITHM;
+        for (Policy p : policies) {
+            if (p instanceof HashAlgorithm) {
+                return ((HashAlgorithm) p).algorithm;
+            }
+
+        }
+        return Constants.DEFAULT_HASH_ALGORITHM;
+    }
+
     /**
      *
      * @return -1 if no hash iterations setting
@@ -131,9 +146,9 @@ public class PasswordPolicy implements Serializable {
        return -1;
    }
 
-    public Error validate(UserModel user, String password) {
+    public Error validate(KeycloakSession session, UserModel user, String password) {
         for (Policy p : policies) {
-            Error error = p.validate(user, password);
+            Error error = p.validate(session, user, password);
             if (error != null) {
                 return error;
             }
@@ -141,9 +156,9 @@ public class PasswordPolicy implements Serializable {
         return null;
     }
     
-    public Error validate(String user, String password) {
+    public Error validate(KeycloakSession session, String user, String password) {
         for (Policy p : policies) {
-            Error error = p.validate(user, password);
+            Error error = p.validate(session, user, password);
             if (error != null) {
                 return error;
             }
@@ -152,8 +167,8 @@ public class PasswordPolicy implements Serializable {
     }
 
     private static interface Policy extends Serializable {
-        public Error validate(UserModel user, String password);
-        public Error validate(String user, String password);
+        public Error validate(KeycloakSession session, UserModel user, String password);
+        public Error validate(KeycloakSession session, String user, String password);
     }
 
     public static class Error {
@@ -174,6 +189,25 @@ public class PasswordPolicy implements Serializable {
         }
     }
 
+    private static class HashAlgorithm implements Policy {
+        private static final String NAME = "hashAlgorithm";
+        private String algorithm;
+
+        public HashAlgorithm(String arg) {
+            algorithm = stringArg(NAME, Constants.DEFAULT_HASH_ALGORITHM, arg);
+        }
+        
+        @Override
+        public Error validate(KeycloakSession session, String user, String password) {
+            return null;
+        }
+        
+        @Override
+        public Error validate(KeycloakSession session, UserModel user, String password) {
+            return null;
+        }
+    }
+
     private static class HashIterations implements Policy {
         private static final String NAME = "hashIterations";
         private int iterations;
@@ -181,14 +215,14 @@ public class PasswordPolicy implements Serializable {
         public HashIterations(String arg) {
             iterations = intArg(NAME, 1, arg);
         }
-        
+
         @Override
-        public Error validate(String user, String password) {
+        public Error validate(KeycloakSession session, String user, String password) {
             return null;
         }
-        
+
         @Override
-        public Error validate(UserModel user, String password) {
+        public Error validate(KeycloakSession session, UserModel user, String password) {
             return null;
         }
     }
@@ -200,13 +234,13 @@ public class PasswordPolicy implements Serializable {
         }
 
         @Override
-        public Error validate(String username, String password) {
+        public Error validate(KeycloakSession session, String username, String password) {
             return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null;
         }
         
         @Override
-        public Error validate(UserModel user, String password) {
-            return validate(user.getUsername(), password);
+        public Error validate(KeycloakSession session, UserModel user, String password) {
+            return validate(session, user.getUsername(), password);
         }
     }
 
@@ -221,13 +255,13 @@ public class PasswordPolicy implements Serializable {
         
 
         @Override
-        public Error validate(String username, String password) {
+        public Error validate(KeycloakSession session, String username, String password) {
             return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null;
         }
         
         @Override
-        public Error validate(UserModel user, String password) {
-            return validate(user.getUsername(), password);
+        public Error validate(KeycloakSession session, UserModel user, String password) {
+            return validate(session, user.getUsername(), password);
         }
     }
 
@@ -242,7 +276,7 @@ public class PasswordPolicy implements Serializable {
         
 
         @Override
-        public Error validate(String username, String password) {
+        public Error validate(KeycloakSession session, String username, String password) {
             int count = 0;
             for (char c : password.toCharArray()) {
                 if (Character.isDigit(c)) {
@@ -253,8 +287,8 @@ public class PasswordPolicy implements Serializable {
         }
         
         @Override
-        public Error validate(UserModel user, String password) {
-            return validate(user.getUsername(), password);
+        public Error validate(KeycloakSession session, UserModel user, String password) {
+            return validate(session, user.getUsername(), password);
         }
     }
 
@@ -268,7 +302,7 @@ public class PasswordPolicy implements Serializable {
         }
         
         @Override
-        public Error validate(String username, String password) {
+        public Error validate(KeycloakSession session, String username, String password) {
             int count = 0;
             for (char c : password.toCharArray()) {
                 if (Character.isLowerCase(c)) {
@@ -279,8 +313,8 @@ public class PasswordPolicy implements Serializable {
         }
         
         @Override
-        public Error validate(UserModel user, String password) {
-            return validate(user.getUsername(), password);
+        public Error validate(KeycloakSession session, UserModel user, String password) {
+            return validate(session, user.getUsername(), password);
         }
     }
 
@@ -293,7 +327,7 @@ public class PasswordPolicy implements Serializable {
         }
 
         @Override
-        public Error validate(String username, String password) {
+        public Error validate(KeycloakSession session, String username, String password) {
             int count = 0;
             for (char c : password.toCharArray()) {
                 if (Character.isUpperCase(c)) {
@@ -304,8 +338,8 @@ public class PasswordPolicy implements Serializable {
         }
         
         @Override
-        public Error validate(UserModel user, String password) {
-            return validate(user.getUsername(), password);
+        public Error validate(KeycloakSession session, UserModel user, String password) {
+            return validate(session, user.getUsername(), password);
         }
     }
 
@@ -319,7 +353,7 @@ public class PasswordPolicy implements Serializable {
         }
         
         @Override
-        public Error validate(String username, String password) {
+        public Error validate(KeycloakSession session, String username, String password) {
             int count = 0;
             for (char c : password.toCharArray()) {
                 if (!Character.isLetterOrDigit(c)) {
@@ -330,8 +364,8 @@ public class PasswordPolicy implements Serializable {
         }
         
         @Override
-        public Error validate(UserModel user, String password) {
-            return validate(user.getUsername(), password);
+        public Error validate(KeycloakSession session, UserModel user, String password) {
+            return validate(session, user.getUsername(), password);
         }
     }
 
@@ -345,7 +379,7 @@ public class PasswordPolicy implements Serializable {
         }
 
         @Override
-        public Error validate(String username, String password) {
+        public Error validate(KeycloakSession session, String username, String password) {
             Pattern pattern = Pattern.compile(regexPattern);
             Matcher matcher = pattern.matcher(password);
             if (!matcher.matches()) {
@@ -355,8 +389,8 @@ public class PasswordPolicy implements Serializable {
         }
 
         @Override
-        public Error validate(UserModel user, String password) {
-            return validate(user.getUsername(), password);
+        public Error validate(KeycloakSession session, UserModel user, String password) {
+            return validate(session, user.getUsername(), password);
         }
     }
 
@@ -370,18 +404,19 @@ public class PasswordPolicy implements Serializable {
         }
         
         @Override
-        public Error validate(String user, String password) {
+        public Error validate(KeycloakSession session, String user, String password) {
             return null;
         }
 
         @Override
-        public Error validate(UserModel user, String password) {
+        public Error validate(KeycloakSession session, UserModel user, String password) {
             
             if (passwordHistoryPolicyValue != -1) {
             
                 UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD);
                 if (cred != null) {
-                    if(new Pbkdf2PasswordEncoder(cred.getSalt()).verify(password, cred.getValue(), cred.getHashIterations())) {
+                    PasswordHashProvider hashProvider = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
+                    if(hashProvider.verify(password, cred)) {
                         return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
                     }
                 }
@@ -389,7 +424,8 @@ public class PasswordPolicy implements Serializable {
                 List<UserCredentialValueModel> passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1,
                         UserCredentialModel.PASSWORD_HISTORY);
                 for (UserCredentialValueModel credential : passwordExpiredCredentials) {
-                    if (new Pbkdf2PasswordEncoder(credential.getSalt()).verify(password, credential.getValue(), credential.getHashIterations())) {
+                    PasswordHashProvider hashProvider = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
+                    if (hashProvider.verify(password, credential)) {
                         return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
                     }
                 }
@@ -444,12 +480,12 @@ public class PasswordPolicy implements Serializable {
         }
 
         @Override
-        public Error validate(String username, String password) {
+        public Error validate(KeycloakSession session, String username, String password) {
             return null;
         }
 
         @Override
-        public Error validate(UserModel user, String password) {
+        public Error validate(KeycloakSession session, UserModel user, String password) {
             return null;
         }
     }
@@ -462,6 +498,14 @@ public class PasswordPolicy implements Serializable {
         }
     }
 
+    private static String stringArg(String policy, String defaultValue, String arg) {
+        if (arg == null) {
+            return defaultValue;
+        } else {
+            return arg;
+        }
+    }
+
     @Override
     public String toString() {
         return policyString;
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index 8c64daf..58344cc 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -395,7 +395,7 @@ public class UserFederationManager implements UserProvider {
     public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
         if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
             if (realm.getPasswordPolicy() != null) {
-                PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user, credential.getValue());
+                PasswordPolicy.Error error = realm.getPasswordPolicy().validate(session, user, credential.getValue());
                 if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
             }
         }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index 49623e5..6afc9ed 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -1,7 +1,6 @@
 package org.keycloak.models.utils;
 
-import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
-
+import org.keycloak.hash.PasswordHashManager;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.JWSInputException;
 import org.keycloak.jose.jws.crypto.RSAProvider;
@@ -17,7 +16,6 @@ import org.keycloak.representations.PasswordToken;
 import org.keycloak.common.util.Time;
 import org.keycloak.hash.PasswordHashProvider;
 
-import java.io.IOException;
 import java.util.List;
 
 /**
@@ -61,30 +59,14 @@ public class CredentialValidation {
         if(unhashedCredValue == null){
             return false;
         }
-        String algorithm;
-        if (credential.getAlgorithm() == null || credential.getAlgorithm().trim().equals("")) {
-            algorithm = Constants.DEFAULT_HASH_ALGORITHM;
-        } else {
-            algorithm = credential.getAlgorithm();
-        }
-
-        byte[] salt = getSalt();
 
-        PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, algorithm);
-
-        byte[] credSalt = credential.getSalt();
-
-        boolean validated = provider.verify(unhashedCredValue, credential.getValue(), credSalt);
+        boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential);
 
         if (validated) {
             int iterations = hashIterations(realm);
             if (iterations > -1 && iterations != credential.getHashIterations()) {
-                UserCredentialValueModel newCred = new UserCredentialValueModel();
-                newCred.setType(credential.getType());
-                newCred.setDevice(credential.getDevice());
-                newCred.setSalt(credential.getSalt());
-                newCred.setHashIterations(iterations);
-                newCred.setValue(provider.encode(unhashedCredValue, salt, iterations));
+
+                UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue);
                 user.updateCredentialDirectly(newCred);
             }
 
diff --git a/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory b/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory
index 56193f6..f29ba9c 100644
--- a/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory
+++ b/model/api/src/main/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory
@@ -1 +1 @@
-org.keycloak.hash.DefaultPasswordHashProviderFactory
+org.keycloak.hash.Pbkdf2PasswordHashProvider
\ No newline at end of file
diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
index 8c662fb..f910ea1 100755
--- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
+++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
@@ -15,73 +15,73 @@ public class PasswordPolicyTest {
     @Test
     public void testLength() {
         PasswordPolicy policy = new PasswordPolicy("length");
-        Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "1234567").getMessage());
-        Assert.assertArrayEquals(new Object[]{8}, policy.validate("jdoe", "1234567").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "12345678"));
+        Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate(null, "jdoe", "1234567").getMessage());
+        Assert.assertArrayEquals(new Object[]{8}, policy.validate(null, "jdoe", "1234567").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "12345678"));
 
         policy = new PasswordPolicy("length(4)");
-        Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "123").getMessage());
-        Assert.assertArrayEquals(new Object[]{4}, policy.validate("jdoe", "123").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "1234"));
+        Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate(null, "jdoe", "123").getMessage());
+        Assert.assertArrayEquals(new Object[]{4}, policy.validate(null, "jdoe", "123").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "1234"));
     }
 
     @Test
     public void testDigits() {
         PasswordPolicy policy = new PasswordPolicy("digits");
-        Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd").getMessage());
-        Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "abcd1"));
+        Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate(null, "jdoe", "abcd").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "abcd1"));
 
         policy = new PasswordPolicy("digits(2)");
-        Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd1").getMessage());
-        Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abcd1").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "abcd12"));
+        Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate(null, "jdoe", "abcd1").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "abcd1").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "abcd12"));
     }
 
     @Test
     public void testLowerCase() {
         PasswordPolicy policy = new PasswordPolicy("lowerCase");
-        Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABCD1234").getMessage());
-        Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "ABCD1234").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "ABcD1234"));
+        Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate(null, "jdoe", "ABCD1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "ABCD1234").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "ABcD1234"));
 
         policy = new PasswordPolicy("lowerCase(2)");
-        Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABcD1234").getMessage());
-        Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ABcD1234").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "aBcD1234"));
+        Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate(null, "jdoe", "ABcD1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "ABcD1234").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "aBcD1234"));
     }
 
     @Test
     public void testUpperCase() {
         PasswordPolicy policy = new PasswordPolicy("upperCase");
-        Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abcd1234").getMessage());
-        Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "abCd1234"));
+        Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate(null, "jdoe", "abcd1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd1234").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "abCd1234"));
 
         policy = new PasswordPolicy("upperCase(2)");
-        Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abCd1234").getMessage());
-        Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abCd1234").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "AbCd1234"));
+        Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate(null, "jdoe", "abCd1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "abCd1234").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "AbCd1234"));
     }
 
     @Test
     public void testSpecialChars() {
         PasswordPolicy policy = new PasswordPolicy("specialChars");
-        Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "abcd1234").getMessage());
-        Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
+        Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate(null, "jdoe", "abcd1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd1234").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "ab&d1234"));
 
         policy = new PasswordPolicy("specialChars(2)");
-        Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "ab&d1234").getMessage());
-        Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ab&d1234").getParameters());
-        Assert.assertNull(policy.validate("jdoe", "ab&d-234"));
+        Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate(null, "jdoe", "ab&d1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "ab&d1234").getParameters());
+        Assert.assertNull(policy.validate(null, "jdoe", "ab&d-234"));
     }
 
     @Test
     public void testNotUsername() {
         PasswordPolicy policy = new PasswordPolicy("notUsername");
-        Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
-        Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
+        Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
+        Assert.assertNull(policy.validate(null, "jdoe", "ab&d1234"));
     }
 
     @Test
@@ -119,33 +119,33 @@ public class PasswordPolicyTest {
         
         //Fails to match one of the regex pattern
         policy = new PasswordPolicy("regexPattern(jdoe) and regexPattern(j*d)");
-        Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
+        Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
         
         ////Fails to match all of the regex patterns
         policy = new PasswordPolicy("regexPattern(j*p) and regexPattern(j*d) and regexPattern(adoe)");
-        Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
+        Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
         
         policy = new PasswordPolicy("regexPattern([a-z][a-z][a-z][a-z][0-9])");
-        Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
+        Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
         
         policy = new PasswordPolicy("regexPattern(jdoe)");
-        Assert.assertNull(policy.validate("jdoe", "jdoe"));
+        Assert.assertNull(policy.validate(null, "jdoe", "jdoe"));
         
         policy = new PasswordPolicy("regexPattern([a-z][a-z][a-z][a-z][0-9])");
-        Assert.assertNull(policy.validate("jdoe", "jdoe0"));
+        Assert.assertNull(policy.validate(null, "jdoe", "jdoe0"));
     }
 
     @Test
     public void testComplex() {
         PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()");
-        Assert.assertNotNull(policy.validate("jdoe", "12aaBB&"));
-        Assert.assertNotNull(policy.validate("jdoe", "aaaaBB&-"));
-        Assert.assertNotNull(policy.validate("jdoe", "12AABB&-"));
-        Assert.assertNotNull(policy.validate("jdoe", "12aabb&-"));
-        Assert.assertNotNull(policy.validate("jdoe", "12aaBBcc"));
-        Assert.assertNotNull(policy.validate("12aaBB&-", "12aaBB&-"));
-
-        Assert.assertNull(policy.validate("jdoe", "12aaBB&-"));
+        Assert.assertNotNull(policy.validate(null, "jdoe", "12aaBB&"));
+        Assert.assertNotNull(policy.validate(null, "jdoe", "aaaaBB&-"));
+        Assert.assertNotNull(policy.validate(null, "jdoe", "12AABB&-"));
+        Assert.assertNotNull(policy.validate(null, "jdoe", "12aabb&-"));
+        Assert.assertNotNull(policy.validate(null, "jdoe", "12aaBBcc"));
+        Assert.assertNotNull(policy.validate(null, "12aaBB&-", "12aaBB&-"));
+
+        Assert.assertNull(policy.validate(null, "jdoe", "12aaBB&-"));
     }
 
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 8594bca..ded42a7 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -1,5 +1,6 @@
 package org.keycloak.models.jpa;
 
+import org.keycloak.hash.PasswordHashManager;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
@@ -42,8 +43,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
-
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
@@ -390,19 +389,12 @@ public class UserAdapter implements UserModel {
     }
 
     private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
-        byte[] salt = getSalt();
-        int hashIterations = 1;
-        PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class);
-        PasswordPolicy policy = realm.getPasswordPolicy();
-        if (policy != null) {
-            hashIterations = policy.getHashIterations();
-            if (hashIterations == -1)
-                hashIterations = 1;
-        }
+        UserCredentialValueModel encoded = PasswordHashManager.encode(session, realm, cred.getValue());
         credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime()));
-        credentialEntity.setValue(provider.encode(cred.getValue(), salt, hashIterations));
-        credentialEntity.setSalt(salt);
-        credentialEntity.setHashIterations(hashIterations);
+        credentialEntity.setAlgorithm(encoded.getAlgorithm());
+        credentialEntity.setValue(encoded.getValue());
+        credentialEntity.setSalt(encoded.getSalt());
+        credentialEntity.setHashIterations(encoded.getHashIterations());
     }
 
     private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 3137ab4..5e822b0 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -1,11 +1,10 @@
 package org.keycloak.models.mongo.keycloak.adapters;
 
-import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
-
 import com.mongodb.DBObject;
 import com.mongodb.QueryBuilder;
 
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.hash.PasswordHashManager;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.OTPPolicy;
@@ -329,19 +328,12 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     }
 
     private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
-        byte[] salt = getSalt();
-        int hashIterations = 1;
-        PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class);
-        PasswordPolicy policy = realm.getPasswordPolicy();
-        if (policy != null) {
-            hashIterations = policy.getHashIterations();
-            if (hashIterations == -1)
-                hashIterations = 1;
-        }
+        UserCredentialValueModel encoded = PasswordHashManager.encode(session, realm, cred.getValue());
         credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime()));
-        credentialEntity.setValue(provider.encode(cred.getValue(), salt, hashIterations));
-        credentialEntity.setSalt(salt);
-        credentialEntity.setHashIterations(hashIterations);
+        credentialEntity.setAlgorithm(encoded.getAlgorithm());
+        credentialEntity.setValue(encoded.getValue());
+        credentialEntity.setSalt(encoded.getSalt());
+        credentialEntity.setHashIterations(encoded.getHashIterations());
     }
 
     private CredentialEntity getCredentialEntity(MongoUserEntity userEntity, String credType) {
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java
index ff2442f..027419c 100755
--- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java
@@ -53,7 +53,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
             errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM));
         }
         if (formData.getFirst(RegistrationPage.FIELD_PASSWORD) != null) {
-            PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD));
+            PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getSession(), context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD));
             if (err != null)
                 errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, err.getMessage(), err.getParameters()));
         }
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
index 0a3c9bb..0c3dcc0 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -1,6 +1,7 @@
 package org.keycloak.services.validation;
 
 import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.FormMessage;
@@ -25,7 +26,7 @@ public class Validation {
     // Actually allow same emails like angular. See ValidationTest.testEmailValidation()
     private static final Pattern EMAIL_PATTERN = Pattern.compile("[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*");
 
-    public static List<FormMessage> validateRegistrationForm(RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes, PasswordPolicy policy) {
+    public static List<FormMessage> validateRegistrationForm(KeycloakSession session, RealmModel realm, MultivaluedMap<String, String> formData, List<String> requiredCredentialTypes, PasswordPolicy policy) {
         List<FormMessage> errors = new ArrayList<>();
 
         if (!realm.isRegistrationEmailAsUsername() && isBlank(formData.getFirst(FIELD_USERNAME))) {
@@ -55,7 +56,7 @@ public class Validation {
         }
 
         if (formData.getFirst(FIELD_PASSWORD) != null) {
-            PasswordPolicy.Error err = policy.validate(realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
+            PasswordPolicy.Error err = policy.validate(session, realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
             if (err != null)
                 errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters()));
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
index dea6ebc..464f376 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java
@@ -1,16 +1,17 @@
 package org.keycloak.testsuite.adduser;
 
 import org.codehaus.jackson.type.TypeReference;
-import org.junit.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.keycloak.admin.client.Keycloak;
 import org.keycloak.admin.client.resource.RealmResource;
-import org.keycloak.admin.client.resource.RoleMappingResource;
-import org.keycloak.admin.client.resource.RoleScopeResource;
 import org.keycloak.admin.client.resource.UserResource;
-import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.Base64;
+import org.keycloak.hash.Pbkdf2PasswordHashProvider;
 import org.keycloak.models.Constants;
-import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
 import org.keycloak.representations.idm.*;
 import org.keycloak.testsuite.KeycloakServer;
 import org.keycloak.util.JsonSerialization;
@@ -19,7 +20,6 @@ import org.keycloak.wildfly.adduser.AddUser;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.Collections;
 import java.util.List;
 
 import static org.junit.Assert.*;
@@ -59,7 +59,11 @@ public class AddUserTest {
         UserRepresentation user = realms.get(0).getUsers().get(0);
         assertEquals(new Integer(100000), user.getCredentials().get(0).getHashIterations());
         assertNull(user.getCredentials().get(0).getValue());
-        assertEquals(new Pbkdf2PasswordEncoder(Base64Url.decode(user.getCredentials().get(0).getSalt()), 100000).encode("password"), user.getCredentials().get(0).getHashedSaltedValue());
+
+        CredentialRepresentation credentials = user.getCredentials().get(0);
+
+        assertEquals(Pbkdf2PasswordHashProvider.ID, credentials.getAlgorithm());
+        assertEquals(new Integer(100000), credentials.getHashIterations());
 
         KeycloakServer server = new KeycloakServer();
         try {
diff --git a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
index 47e1400..d741333 100644
--- a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
+++ b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java
@@ -12,9 +12,8 @@ import org.jboss.aesh.console.command.invocation.CommandInvocation;
 import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder;
 import org.jboss.aesh.console.command.registry.CommandRegistry;
 import org.keycloak.common.util.Base64;
-import org.keycloak.hash.DefaultPasswordHashProvider;
-import org.keycloak.models.Constants;
-import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
+import org.keycloak.hash.Pbkdf2PasswordHashProvider;
+import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
@@ -139,14 +138,14 @@ public class AddUser {
         user.setUsername(userName);
         user.setCredentials(new LinkedList<CredentialRepresentation>());
 
-        byte[] salt = Pbkdf2PasswordEncoder.getSalt();
-        iterations = iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS;
+        UserCredentialValueModel credentialValueModel = new Pbkdf2PasswordHashProvider().encode(password, iterations > 0 ? iterations : DEFAULT_HASH_ITERATIONS);
 
         CredentialRepresentation credentials = new CredentialRepresentation();
-        credentials.setType(CredentialRepresentation.PASSWORD);
-        credentials.setHashIterations(iterations);
-        credentials.setSalt(Base64.encodeBytes(salt));
-        credentials.setHashedSaltedValue(new DefaultPasswordHashProvider().encode(password, salt, iterations));
+        credentials.setType(credentialValueModel.getType());
+        credentials.setAlgorithm(credentialValueModel.getAlgorithm());
+        credentials.setHashIterations(credentialValueModel.getHashIterations());
+        credentials.setSalt(Base64.encodeBytes(credentialValueModel.getSalt()));
+        credentials.setHashedSaltedValue(credentialValueModel.getValue());
 
         user.getCredentials().add(credentials);