Browse Source

Configurable password hashing algorithm/cost (#31234)

Make password hashing algorithm/cost configurable for the 
stored passwords of users for the realms that this applies
(native, reserved). Replaces predefined choice of bcrypt with
cost factor 10.
This also introduces PBKDF2 with configurable cost
(number of iterations) as an algorithm option for password hashing
both for storing passwords and for the user cache.
Password hash validation algorithm selection takes into
consideration the stored hash prefix and only a specific number
of algorithnm and cost factor options for brypt and pbkdf2 are 
whitelisted and can be selected in the relevant setting.
Ioannis Kakavas 7 years ago
parent
commit
db6b33978e
59 changed files with 982 additions and 391 deletions
  1. 12 0
      server/src/main/java/org/elasticsearch/common/settings/Setting.java
  2. 20 0
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java
  3. 7 6
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/ChangePasswordRequestBuilder.java
  4. 4 6
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/PutUserRequestBuilder.java
  5. 2 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/CachingUsernamePasswordRealmSettings.java
  6. 348 76
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java
  7. 11 9
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java
  8. 0 2
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/XPackSettingsTests.java
  9. 41 0
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheck.java
  10. 4 3
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java
  11. 9 0
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordAction.java
  12. 14 6
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java
  13. 15 12
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java
  14. 8 0
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/UserAndPassword.java
  15. 2 4
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java
  16. 6 5
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java
  17. 4 6
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java
  18. 4 1
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestChangePasswordAction.java
  19. 5 1
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestPutUserAction.java
  20. 0 3
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java
  21. 3 3
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java
  22. 9 6
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java
  23. 3 3
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java
  24. 7 7
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java
  25. 3 3
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityRandomTests.java
  26. 4 5
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java
  27. 6 6
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java
  28. 9 10
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java
  29. 19 17
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java
  30. 2 3
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java
  31. 2 3
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/KibanaUserRoleIntegTests.java
  32. 11 11
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/MultipleIndicesPermissionsTests.java
  33. 4 5
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/PermissionPrecedenceTests.java
  34. 3 4
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/SecurityClearScrollTests.java
  35. 3 6
      x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java
  36. 5 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java
  37. 4 2
      x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java
  38. 44 0
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheckTests.java
  39. 7 6
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java
  40. 53 11
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordActionTests.java
  41. 2 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportPutUserActionTests.java
  42. 4 5
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java
  43. 1 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java
  44. 47 34
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java
  45. 3 2
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java
  46. 1 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java
  47. 47 32
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java
  48. 4 3
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java
  49. 27 14
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java
  50. 10 12
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java
  51. 83 10
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/HasherTests.java
  52. 4 4
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AnalyzeTests.java
  53. 7 8
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java
  54. 3 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java
  55. 2 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerIntegTests.java
  56. 6 1
      x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/file/users
  57. 2 1
      x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/role/CustomRolesProviderIT.java
  58. 2 1
      x-pack/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java
  59. 10 6
      x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java

+ 12 - 0
server/src/main/java/org/elasticsearch/common/settings/Setting.java

@@ -1017,6 +1017,18 @@ public class Setting<T> implements ToXContentObject {
         return new Setting<>(new SimpleKey(key), null, s -> "", Function.identity(), validator, properties);
     }
 
+    /**
+     * Creates a new Setting instance with a String value
+     *
+     * @param key          the settings key for this setting.
+     * @param defaultValue the default String value.
+     * @param properties   properties for this setting like scope, filtering...
+     * @return the Setting Object
+     */
+    public static Setting<String> simpleString(String key, String defaultValue, Property... properties) {
+        return new Setting<>(key, s -> defaultValue, Function.identity(), properties);
+    }
+
     public static int parseInt(String s, int minValue, String key) {
         return parseInt(s, minValue, Integer.MAX_VALUE, key);
     }

+ 20 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java

@@ -8,6 +8,7 @@ package org.elasticsearch.xpack.core;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Setting.Property;
 import org.elasticsearch.xpack.core.security.SecurityField;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.ssl.SSLClientAuth;
 import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings;
 import org.elasticsearch.xpack.core.ssl.VerificationMode;
@@ -19,6 +20,8 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
+import java.util.function.Function;
 
 import static org.elasticsearch.xpack.core.security.SecurityField.USER_SETTING;
 
@@ -26,6 +29,11 @@ import static org.elasticsearch.xpack.core.security.SecurityField.USER_SETTING;
  * A container for xpack setting constants.
  */
 public class XPackSettings {
+
+    private XPackSettings() {
+        throw new IllegalStateException("Utility class should not be instantiated");
+    }
+
     /** Setting for enabling or disabling security. Defaults to true. */
     public static final Setting<Boolean> SECURITY_ENABLED = Setting.boolSetting("xpack.security.enabled", true, Setting.Property.NodeScope);
 
@@ -105,6 +113,17 @@ public class XPackSettings {
         DEFAULT_CIPHERS = ciphers;
     }
 
+    /*
+     * Do not allow insecure hashing algorithms to be used for password hashing
+     */
+    public static final Setting<String> PASSWORD_HASHING_ALGORITHM = new Setting<>(
+        "xpack.security.authc.password_hashing.algorithm", "bcrypt", Function.identity(), (v, s) -> {
+        if (Hasher.getAvailableAlgoStoredHash().contains(v.toLowerCase(Locale.ROOT)) == false) {
+            throw new IllegalArgumentException("Invalid algorithm: " + v + ". Only pbkdf2 or bcrypt family algorithms can be used for " +
+                "password hashing.");
+        }
+    }, Setting.Property.NodeScope);
+
     public static final List<String> DEFAULT_SUPPORTED_PROTOCOLS = Arrays.asList("TLSv1.2", "TLSv1.1", "TLSv1");
     public static final SSLClientAuth CLIENT_AUTH_DEFAULT = SSLClientAuth.REQUIRED;
     public static final SSLClientAuth HTTP_CLIENT_AUTH_DEFAULT = SSLClientAuth.NONE;
@@ -143,6 +162,7 @@ public class XPackSettings {
         settings.add(SQL_ENABLED);
         settings.add(USER_SETTING);
         settings.add(ROLLUP_ENABLED);
+        settings.add(PASSWORD_HASHING_ALGORITHM);
         return Collections.unmodifiableList(settings);
     }
 

+ 7 - 6
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/ChangePasswordRequestBuilder.java

@@ -41,22 +41,22 @@ public class ChangePasswordRequestBuilder
         return this;
     }
 
-    public static char[] validateAndHashPassword(SecureString password) {
+    public static char[] validateAndHashPassword(SecureString password, Hasher hasher) {
         Validation.Error error = Validation.Users.validatePassword(password.getChars());
         if (error != null) {
             ValidationException validationException = new ValidationException();
             validationException.addValidationError(error.toString());
             throw validationException;
         }
-        return Hasher.BCRYPT.hash(password);
+        return hasher.hash(password);
     }
 
     /**
      * Sets the password. Note: the char[] passed to this method will be cleared.
      */
-    public ChangePasswordRequestBuilder password(char[] password) {
+    public ChangePasswordRequestBuilder password(char[] password, Hasher hasher) {
         try (SecureString secureString = new SecureString(password)) {
-            char[] hash = validateAndHashPassword(secureString);
+            char[] hash = validateAndHashPassword(secureString, hasher);
             request.passwordHash(hash);
         }
         return this;
@@ -65,7 +65,8 @@ public class ChangePasswordRequestBuilder
     /**
      * Populate the change password request from the source in the provided content type
      */
-    public ChangePasswordRequestBuilder source(BytesReference source, XContentType xContentType) throws IOException {
+    public ChangePasswordRequestBuilder source(BytesReference source, XContentType xContentType, Hasher hasher) throws
+        IOException {
         // EMPTY is ok here because we never call namedObject
         try (InputStream stream = source.streamInput();
              XContentParser parser = xContentType.xContent()
@@ -80,7 +81,7 @@ public class ChangePasswordRequestBuilder
                     if (token == XContentParser.Token.VALUE_STRING) {
                         String password = parser.text();
                         final char[] passwordChars = password.toCharArray();
-                        password(passwordChars);
+                        password(passwordChars, hasher);
                         assert CharBuffer.wrap(passwordChars).chars().noneMatch((i) -> (char) i != (char) 0) : "expected password to " +
                                 "clear the char[] but it did not!";
                     } else {

+ 4 - 6
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/PutUserRequestBuilder.java

@@ -9,7 +9,6 @@ import org.elasticsearch.ElasticsearchParseException;
 import org.elasticsearch.action.ActionRequestBuilder;
 import org.elasticsearch.action.support.WriteRequestBuilder;
 import org.elasticsearch.client.ElasticsearchClient;
-import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.ValidationException;
 import org.elasticsearch.common.bytes.BytesReference;
@@ -33,8 +32,6 @@ import java.util.Objects;
 public class PutUserRequestBuilder extends ActionRequestBuilder<PutUserRequest, PutUserResponse>
         implements WriteRequestBuilder<PutUserRequestBuilder> {
 
-    private final Hasher hasher = Hasher.BCRYPT;
-
     public PutUserRequestBuilder(ElasticsearchClient client) {
         this(client, PutUserAction.INSTANCE);
     }
@@ -53,7 +50,7 @@ public class PutUserRequestBuilder extends ActionRequestBuilder<PutUserRequest,
         return this;
     }
 
-    public PutUserRequestBuilder password(@Nullable char[] password) {
+    public PutUserRequestBuilder password(char[] password, Hasher hasher) {
         if (password != null) {
             Validation.Error error = Validation.Users.validatePassword(password);
             if (error != null) {
@@ -96,7 +93,8 @@ public class PutUserRequestBuilder extends ActionRequestBuilder<PutUserRequest,
     /**
      * Populate the put user request using the given source and username
      */
-    public PutUserRequestBuilder source(String username, BytesReference source, XContentType xContentType) throws IOException {
+    public PutUserRequestBuilder source(String username, BytesReference source, XContentType xContentType, Hasher hasher) throws
+        IOException {
         Objects.requireNonNull(xContentType);
         username(username);
         // EMPTY is ok here because we never call namedObject
@@ -113,7 +111,7 @@ public class PutUserRequestBuilder extends ActionRequestBuilder<PutUserRequest,
                     if (token == XContentParser.Token.VALUE_STRING) {
                         String password = parser.text();
                         char[] passwordChars = password.toCharArray();
-                        password(passwordChars);
+                        password(passwordChars, hasher);
                         Arrays.fill(passwordChars, (char) 0);
                     } else {
                         throw new ElasticsearchParseException(

+ 2 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/CachingUsernamePasswordRealmSettings.java

@@ -13,7 +13,8 @@ import java.util.HashSet;
 import java.util.Set;
 
 public final class CachingUsernamePasswordRealmSettings {
-    public static final Setting<String> CACHE_HASH_ALGO_SETTING = Setting.simpleString("cache.hash_algo", Setting.Property.NodeScope);
+    public static final Setting<String> CACHE_HASH_ALGO_SETTING = Setting.simpleString("cache.hash_algo", "ssha256",
+        Setting.Property.NodeScope);
     private static final TimeValue DEFAULT_TTL = TimeValue.timeValueMinutes(20);
     public static final Setting<TimeValue> CACHE_TTL_SETTING = Setting.timeSetting("cache.ttl", DEFAULT_TTL, Setting.Property.NodeScope);
     private static final int DEFAULT_MAX_USERS = 100_000; //100k users

+ 348 - 76
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/Hasher.java

@@ -5,15 +5,22 @@
  */
 package org.elasticsearch.xpack.core.security.authc.support;
 
-import org.elasticsearch.common.Randomness;
+import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.common.hash.MessageDigests;
 import org.elasticsearch.common.settings.SecureString;
 
-import java.nio.charset.StandardCharsets;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import java.nio.CharBuffer;
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
 import java.util.Base64;
+import java.util.List;
 import java.util.Locale;
-import java.util.Random;
+import java.util.stream.Collectors;
 
 public enum Hasher {
 
@@ -26,11 +33,7 @@ public enum Hasher {
 
         @Override
         public boolean verify(SecureString text, char[] hash) {
-            String hashStr = new String(hash);
-            if (!hashStr.startsWith(BCRYPT_PREFIX)) {
-                return false;
-            }
-            return BCrypt.checkpw(text, hashStr);
+            return verifyBcryptHash(text, hash);
         }
     },
 
@@ -43,11 +46,7 @@ public enum Hasher {
 
         @Override
         public boolean verify(SecureString text, char[] hash) {
-            String hashStr = new String(hash);
-            if (!hashStr.startsWith(BCRYPT_PREFIX)) {
-                return false;
-            }
-            return BCrypt.checkpw(text, hashStr);
+            return verifyBcryptHash(text, hash);
         }
     },
 
@@ -60,11 +59,7 @@ public enum Hasher {
 
         @Override
         public boolean verify(SecureString text, char[] hash) {
-            String hashStr = new String(hash);
-            if (!hashStr.startsWith(BCRYPT_PREFIX)) {
-                return false;
-            }
-            return BCrypt.checkpw(text, hashStr);
+            return verifyBcryptHash(text, hash);
         }
     },
 
@@ -77,11 +72,7 @@ public enum Hasher {
 
         @Override
         public boolean verify(SecureString text, char[] hash) {
-            String hashStr = new String(hash);
-            if (!hashStr.startsWith(BCRYPT_PREFIX)) {
-                return false;
-            }
-            return BCrypt.checkpw(text, hashStr);
+            return verifyBcryptHash(text, hash);
         }
     },
 
@@ -94,11 +85,7 @@ public enum Hasher {
 
         @Override
         public boolean verify(SecureString text, char[] hash) {
-            String hashStr = new String(hash);
-            if (!hashStr.startsWith(BCRYPT_PREFIX)) {
-                return false;
-            }
-            return BCrypt.checkpw(text, hashStr);
+            return verifyBcryptHash(text, hash);
         }
     },
 
@@ -111,11 +98,7 @@ public enum Hasher {
 
         @Override
         public boolean verify(SecureString text, char[] hash) {
-            String hashStr = new String(hash);
-            if (!hashStr.startsWith(BCRYPT_PREFIX)) {
-                return false;
-            }
-            return BCrypt.checkpw(text, hashStr);
+            return verifyBcryptHash(text, hash);
         }
     },
 
@@ -128,12 +111,164 @@ public enum Hasher {
 
         @Override
         public boolean verify(SecureString text, char[] hash) {
-            String hashStr = new String(hash);
-            if (!hashStr.startsWith(BCRYPT_PREFIX)) {
-                return false;
-            }
-            return BCrypt.checkpw(text, hashStr);
+            return verifyBcryptHash(text, hash);
+        }
+    },
+
+    BCRYPT10() {
+        @Override
+        public char[] hash(SecureString text) {
+            String salt = BCrypt.gensalt(10);
+            return BCrypt.hashpw(text, salt).toCharArray();
+        }
+
+        @Override
+        public boolean verify(SecureString text, char[] hash) {
+            return verifyBcryptHash(text, hash);
+        }
+    },
+
+    BCRYPT11() {
+        @Override
+        public char[] hash(SecureString text) {
+            String salt = BCrypt.gensalt(11);
+            return BCrypt.hashpw(text, salt).toCharArray();
+        }
+
+        @Override
+        public boolean verify(SecureString text, char[] hash) {
+            return verifyBcryptHash(text, hash);
+        }
+    },
+
+    BCRYPT12() {
+        @Override
+        public char[] hash(SecureString text) {
+            String salt = BCrypt.gensalt(12);
+            return BCrypt.hashpw(text, salt).toCharArray();
+        }
+
+        @Override
+        public boolean verify(SecureString text, char[] hash) {
+            return verifyBcryptHash(text, hash);
+        }
+    },
+
+    BCRYPT13() {
+        @Override
+        public char[] hash(SecureString text) {
+            String salt = BCrypt.gensalt(13);
+            return BCrypt.hashpw(text, salt).toCharArray();
+        }
+
+        @Override
+        public boolean verify(SecureString text, char[] hash) {
+            return verifyBcryptHash(text, hash);
+        }
+    },
+
+    BCRYPT14() {
+        @Override
+        public char[] hash(SecureString text) {
+            String salt = BCrypt.gensalt(14);
+            return BCrypt.hashpw(text, salt).toCharArray();
+        }
+
+        @Override
+        public boolean verify(SecureString text, char[] hash) {
+            return verifyBcryptHash(text, hash);
+        }
+    },
+
+    PBKDF2() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(data, PBKDF2_DEFAULT_COST);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(data, hash);
         }
+
+    },
+
+    PBKDF2_1000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(data, 1000);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(data, hash);
+        }
+
+    },
+
+    PBKDF2_10000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(data, 10000);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(data, hash);
+        }
+
+    },
+
+    PBKDF2_50000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(data, 50000);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(data, hash);
+        }
+
+    },
+
+    PBKDF2_100000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(data, 100000);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(data, hash);
+        }
+
+    },
+
+    PBKDF2_500000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(data, 500000);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(data, hash);
+        }
+
+    },
+
+    PBKDF2_1000000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(data, 1000000);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(data, hash);
+        }
+
     },
 
     SHA1() {
@@ -149,7 +284,7 @@ public enum Hasher {
         @Override
         public boolean verify(SecureString text, char[] hash) {
             String hashStr = new String(hash);
-            if (!hashStr.startsWith(SHA1_PREFIX)) {
+            if (hashStr.startsWith(SHA1_PREFIX) == false) {
                 return false;
             }
             byte[] textBytes = CharArrays.toUtf8Bytes(text.getChars());
@@ -173,7 +308,7 @@ public enum Hasher {
         @Override
         public boolean verify(SecureString text, char[] hash) {
             String hashStr = new String(hash);
-            if (!hashStr.startsWith(MD5_PREFIX)) {
+            if (hashStr.startsWith(MD5_PREFIX) == false) {
                 return false;
             }
             hashStr = hashStr.substring(MD5_PREFIX.length());
@@ -189,29 +324,30 @@ public enum Hasher {
         public char[] hash(SecureString text) {
             MessageDigest md = MessageDigests.sha256();
             md.update(CharArrays.toUtf8Bytes(text.getChars()));
-            char[] salt = SaltProvider.salt(8);
-            md.update(CharArrays.toUtf8Bytes(salt));
+            byte[] salt = generateSalt(8);
+            md.update(salt);
             String hash = Base64.getEncoder().encodeToString(md.digest());
-            char[] result = new char[SSHA256_PREFIX.length() + salt.length + hash.length()];
+            char[] result = new char[SSHA256_PREFIX.length() + 12 + hash.length()];
             System.arraycopy(SSHA256_PREFIX.toCharArray(), 0, result, 0, SSHA256_PREFIX.length());
-            System.arraycopy(salt, 0, result, SSHA256_PREFIX.length(), salt.length);
-            System.arraycopy(hash.toCharArray(), 0, result, SSHA256_PREFIX.length() + salt.length, hash.length());
+            System.arraycopy(Base64.getEncoder().encodeToString(salt).toCharArray(), 0, result, SSHA256_PREFIX.length(), 12);
+            System.arraycopy(hash.toCharArray(), 0, result, SSHA256_PREFIX.length() + 12, hash.length());
             return result;
         }
 
         @Override
         public boolean verify(SecureString text, char[] hash) {
             String hashStr = new String(hash);
-            if (!hashStr.startsWith(SSHA256_PREFIX)) {
+            if (hashStr.startsWith(SSHA256_PREFIX) == false) {
                 return false;
             }
             hashStr = hashStr.substring(SSHA256_PREFIX.length());
             char[] saltAndHash = hashStr.toCharArray();
             MessageDigest md = MessageDigests.sha256();
             md.update(CharArrays.toUtf8Bytes(text.getChars()));
-            md.update(new String(saltAndHash, 0, 8).getBytes(StandardCharsets.UTF_8));
+            // Base64 string length : (4*(n/3)) rounded up to the next multiple of 4 because of padding, 12 for 8 bytes
+            md.update(Base64.getDecoder().decode(new String(saltAndHash, 0, 12)));
             String computedHash = Base64.getEncoder().encodeToString(md.digest());
-            return CharArrays.constantTimeEquals(computedHash, new String(saltAndHash, 8, saltAndHash.length - 8));
+            return CharArrays.constantTimeEquals(computedHash, new String(saltAndHash, 12, saltAndHash.length - 12));
         }
     },
 
@@ -231,11 +367,22 @@ public enum Hasher {
     private static final String SHA1_PREFIX = "{SHA}";
     private static final String MD5_PREFIX = "{MD5}";
     private static final String SSHA256_PREFIX = "{SSHA256}";
-
-    public static Hasher resolve(String name, Hasher defaultHasher) {
-        if (name == null) {
-            return defaultHasher;
-        }
+    private static final String PBKDF2_PREFIX = "{PBKDF2}";
+    private static final int PBKDF2_DEFAULT_COST = 10000;
+    private static final int PBKDF2_KEY_LENGTH = 256;
+    private static final int BCRYPT_DEFAULT_COST = 10;
+    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
+    /**
+     * Returns a {@link Hasher} instance of the appropriate algorithm and associated cost as
+     * indicated by the {@code name}. Name identifiers for the default costs for
+     * BCRYPT and PBKDF2 return the he default BCRYPT and PBKDF2 Hasher instead of the specific
+     * instances for the associated cost.
+     *
+     * @param name The name of the algorithm and cost combination identifier
+     * @return the hasher associated with the identifier
+     */
+    public static Hasher resolve(String name) {
         switch (name.toLowerCase(Locale.ROOT)) {
             case "bcrypt":
                 return BCRYPT;
@@ -251,6 +398,30 @@ public enum Hasher {
                 return BCRYPT8;
             case "bcrypt9":
                 return BCRYPT9;
+            case "bcrypt10":
+                return BCRYPT;
+            case "bcrypt11":
+                return BCRYPT11;
+            case "bcrypt12":
+                return BCRYPT12;
+            case "bcrypt13":
+                return BCRYPT13;
+            case "bcrypt14":
+                return BCRYPT14;
+            case "pbkdf2":
+                return PBKDF2;
+            case "pbkdf2_1000":
+                return PBKDF2_1000;
+            case "pbkdf2_10000":
+                return PBKDF2;
+            case "pbkdf2_50000":
+                return PBKDF2_50000;
+            case "pbkdf2_100000":
+                return PBKDF2_100000;
+            case "pbkdf2_500000":
+                return PBKDF2_500000;
+            case "pbkdf2_1000000":
+                return PBKDF2_1000000;
             case "sha1":
                 return SHA1;
             case "md5":
@@ -261,38 +432,139 @@ public enum Hasher {
             case "clear_text":
                 return NOOP;
             default:
-                return defaultHasher;
+                throw new IllegalArgumentException("unknown hash function [" + name + "]");
         }
     }
 
-    public static Hasher resolve(String name) {
-        Hasher hasher = resolve(name, null);
-        if (hasher == null) {
-            throw new IllegalArgumentException("unknown hash function [" + name + "]");
+    /**
+     * Returns a {@link Hasher} instance that can be used to verify the {@code hash} by inspecting the
+     * hash prefix and determining the algorithm used for its generation.
+     *
+     * @param hash the char array from which the hashing algorithm is to be deduced
+     * @return the hasher that can be used for validation
+     */
+    public static Hasher resolveFromHash(char[] hash) {
+        if (CharArrays.charsBeginsWith(BCRYPT_PREFIX, hash)) {
+            int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, BCRYPT_PREFIX.length(), hash.length - 54)));
+            return cost == BCRYPT_DEFAULT_COST ? Hasher.BCRYPT : resolve("bcrypt" + cost);
+        } else if (CharArrays.charsBeginsWith(PBKDF2_PREFIX, hash)) {
+            int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, PBKDF2_PREFIX.length(), hash.length - 90)));
+            return cost == PBKDF2_DEFAULT_COST ? Hasher.PBKDF2 : resolve("pbkdf2_" + cost);
+        } else if (CharArrays.charsBeginsWith(SHA1_PREFIX, hash)) {
+            return Hasher.SHA1;
+        } else if (CharArrays.charsBeginsWith(MD5_PREFIX, hash)) {
+            return Hasher.MD5;
+        } else if (CharArrays.charsBeginsWith(SSHA256_PREFIX, hash)) {
+            return Hasher.SSHA256;
+        } else {
+            throw new IllegalArgumentException("unknown hash format for hash [" + new String(hash) + "]");
         }
-        return hasher;
     }
 
-    public abstract char[] hash(SecureString data);
-
-    public abstract boolean verify(SecureString data, char[] hash);
-
-    static final class SaltProvider {
+    /**
+     * Verifies that the cryptographic hash of {@code data} is the same as {@code hash}. The
+     * hashing algorithm and its parameters(cost factor-iterations, salt) are deduced from the
+     * hash itself. The {@code hash} char array is not cleared after verification.
+     *
+     * @param data the SecureString to be hashed and verified
+     * @param hash the char array with the hash against which the string is verified
+     * @return true if the hash corresponds to the data, false otherwise
+     */
+    public static boolean verifyHash(SecureString data, char[] hash) {
+        try {
+            final Hasher hasher = resolveFromHash(hash);
+            return hasher.verify(data, hash);
+        } catch (IllegalArgumentException e) {
+            // The password hash format is invalid, we're unable to verify password
+            return false;
+        }
+    }
 
-        static final char[] ALPHABET = new char[]{
-                '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
-                'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
-                'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
-                'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
-        };
+    private static char[] getPbkdf2Hash(SecureString data, int cost) {
+        try {
+            // Base64 string length : (4*(n/3)) rounded up to the next multiple of 4 because of padding.
+            // n is 32 (PBKDF2_KEY_LENGTH in bytes) and 2 is because of the dollar sign delimiters.
+            CharBuffer result = CharBuffer.allocate(PBKDF2_PREFIX.length() + String.valueOf(cost).length() + 2 + 44 + 44);
+            result.put(PBKDF2_PREFIX);
+            result.put(String.valueOf(cost));
+            result.put("$");
+            byte[] salt = generateSalt(32);
+            result.put(Base64.getEncoder().encodeToString(salt));
+            result.put("$");
+            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHMACSHA512");
+            PBEKeySpec keySpec = new PBEKeySpec(data.getChars(), salt, cost, PBKDF2_KEY_LENGTH);
+            result.put(Base64.getEncoder().encodeToString(secretKeyFactory.generateSecret(keySpec).getEncoded()));
+            return result.array();
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            throw new ElasticsearchException("Can't use PBKDF2 for password hashing", e);
+        }
+    }
 
-        public static char[] salt(int length) {
-            Random random = Randomness.get();
-            char[] salt = new char[length];
-            for (int i = 0; i < length; i++) {
-                salt[i] = ALPHABET[(random.nextInt(ALPHABET.length))];
+    private static boolean verifyPbkdf2Hash(SecureString data, char[] hash) {
+        // Base64 string length : (4*(n/3)) rounded up to the next multiple of 4 because of padding.
+        // n is 32 (PBKDF2_KEY_LENGTH in bytes), so tokenLength is 44
+        final int tokenLength = 44;
+        char[] hashChars = null;
+        char[] saltChars = null;
+        char[] computedPwdHash = null;
+        try {
+            if (CharArrays.charsBeginsWith(PBKDF2_PREFIX, hash) == false) {
+                return false;
+            }
+            hashChars = Arrays.copyOfRange(hash, hash.length - tokenLength, hash.length);
+            saltChars = Arrays.copyOfRange(hash, hash.length - (2 * tokenLength + 1), hash.length - (tokenLength + 1));
+            int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, PBKDF2_PREFIX.length(), hash.length - (2 * tokenLength + 2))));
+            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHMACSHA512");
+            PBEKeySpec keySpec = new PBEKeySpec(data.getChars(), Base64.getDecoder().decode(CharArrays.toUtf8Bytes(saltChars)),
+                cost, PBKDF2_KEY_LENGTH);
+            computedPwdHash = CharArrays.utf8BytesToChars(Base64.getEncoder()
+                .encode(secretKeyFactory.generateSecret(keySpec).getEncoded()));
+            final boolean result = CharArrays.constantTimeEquals(computedPwdHash, hashChars);
+            return result;
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            throw new ElasticsearchException("Can't use PBKDF2 for password hashing", e);
+        } finally {
+            if (null != hashChars) {
+                Arrays.fill(hashChars, '\u0000');
+            }
+            if (null != saltChars) {
+                Arrays.fill(saltChars, '\u0000');
+            }
+            if (null != computedPwdHash) {
+                Arrays.fill(computedPwdHash, '\u0000');
             }
-            return salt;
         }
     }
+
+    private static boolean verifyBcryptHash(SecureString text, char[] hash) {
+        String hashStr = new String(hash);
+        if (hashStr.startsWith(BCRYPT_PREFIX) == false) {
+            return false;
+        }
+        return BCrypt.checkpw(text, hashStr);
+    }
+
+    /**
+     * Returns a list of lower case String identifiers for the Hashing algorithm and parameter
+     * combinations that can be used for password hashing. The identifiers can be used to get
+     * an instance of the appropriate {@link Hasher} by using {@link #resolve(String) resolve()}
+     */
+    public static List<String> getAvailableAlgoStoredHash() {
+        return Arrays.stream(Hasher.values()).map(Hasher::name).map(name -> name.toLowerCase(Locale.ROOT))
+            .filter(name -> (name.startsWith("pbkdf2") || name.startsWith("bcrypt")))
+            .collect(Collectors.toList());
+    }
+
+    public abstract char[] hash(SecureString data);
+
+    public abstract boolean verify(SecureString data, char[] hash);
+
+    /**
+     * Generates an array of {@code length} random bytes using {@link java.security.SecureRandom}
+     */
+    private static byte[] generateSalt(int length) {
+        byte[] salt = new byte[length];
+        SECURE_RANDOM.nextBytes(salt);
+        return salt;
+    }
 }

+ 11 - 9
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java

@@ -76,6 +76,7 @@ import org.elasticsearch.xpack.core.security.action.user.SetEnabledAction;
 import org.elasticsearch.xpack.core.security.action.user.SetEnabledRequest;
 import org.elasticsearch.xpack.core.security.action.user.SetEnabledRequestBuilder;
 import org.elasticsearch.xpack.core.security.action.user.SetEnabledResponse;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 
 import java.io.IOException;
 import java.util.List;
@@ -187,12 +188,13 @@ public class SecurityClient {
         client.execute(DeleteUserAction.INSTANCE, request, listener);
     }
 
-    public PutUserRequestBuilder preparePutUser(String username, BytesReference source, XContentType xContentType) throws IOException {
-        return new PutUserRequestBuilder(client).source(username, source, xContentType);
+    public PutUserRequestBuilder preparePutUser(String username, BytesReference source, XContentType xContentType, Hasher hasher)
+        throws IOException {
+        return new PutUserRequestBuilder(client).source(username, source, xContentType, hasher);
     }
 
-    public PutUserRequestBuilder preparePutUser(String username, char[] password, String... roles) {
-        return new PutUserRequestBuilder(client).username(username).password(password).roles(roles);
+    public PutUserRequestBuilder preparePutUser(String username, char[] password, Hasher hasher, String... roles) {
+        return new PutUserRequestBuilder(client).username(username).password(password, hasher).roles(roles);
     }
 
     public void putUser(PutUserRequest request, ActionListener<PutUserResponse> listener) {
@@ -203,13 +205,13 @@ public class SecurityClient {
      * Populates the {@link ChangePasswordRequest} with the username and password. Note: the passed in char[] will be cleared by this
      * method.
      */
-    public ChangePasswordRequestBuilder prepareChangePassword(String username, char[] password) {
-        return new ChangePasswordRequestBuilder(client).username(username).password(password);
+    public ChangePasswordRequestBuilder prepareChangePassword(String username, char[] password, Hasher hasher) {
+        return new ChangePasswordRequestBuilder(client).username(username).password(password, hasher);
     }
 
-    public ChangePasswordRequestBuilder prepareChangePassword(String username, BytesReference source, XContentType xContentType)
-            throws IOException {
-        return new ChangePasswordRequestBuilder(client).username(username).source(source, xContentType);
+    public ChangePasswordRequestBuilder prepareChangePassword(String username, BytesReference source, XContentType xContentType,
+                                                              Hasher hasher) throws IOException {
+        return new ChangePasswordRequestBuilder(client).username(username).source(source, xContentType, hasher);
     }
 
     public void changePassword(ChangePasswordRequest request, ActionListener<ChangePasswordResponse> listener) {

+ 0 - 2
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/XPackSettingsTests.java

@@ -6,8 +6,6 @@
 package org.elasticsearch.xpack.core;
 
 import org.elasticsearch.test.ESTestCase;
-import org.elasticsearch.xpack.core.XPackSettings;
-
 import javax.crypto.Cipher;
 
 import static org.hamcrest.Matchers.hasItem;

+ 41 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheck.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.security;
+
+import org.elasticsearch.bootstrap.BootstrapCheck;
+import org.elasticsearch.bootstrap.BootstrapContext;
+import org.elasticsearch.xpack.core.XPackSettings;
+
+import javax.crypto.SecretKeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+
+/**
+ * Bootstrap check to ensure that one of the allowed password hashing algorithms is
+ * selected and that it is available.
+ */
+public class PasswordHashingAlgorithmBootstrapCheck implements BootstrapCheck {
+    @Override
+    public BootstrapCheckResult check(BootstrapContext context) {
+        final String selectedAlgorithm = XPackSettings.PASSWORD_HASHING_ALGORITHM.get(context.settings);
+        if (selectedAlgorithm.toLowerCase(Locale.ROOT).startsWith("pbkdf2")) {
+            try {
+                SecretKeyFactory.getInstance("PBKDF2withHMACSHA512");
+            } catch (NoSuchAlgorithmException e) {
+                final String errorMessage = String.format(Locale.ROOT,
+                    "Support for PBKDF2WithHMACSHA512 must be available in order to use any of the " +
+                        "PBKDF2 algorithms for the [%s] setting.", XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey());
+                return BootstrapCheckResult.failure(errorMessage);
+            }
+        }
+        return BootstrapCheckResult.success();
+    }
+
+    @Override
+    public boolean alwaysEnforce() {
+        return true;
+    }
+}

+ 4 - 3
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

@@ -283,9 +283,10 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
             // fetched
             final List<BootstrapCheck> checks = new ArrayList<>();
             checks.addAll(Arrays.asList(
-                    new TokenSSLBootstrapCheck(),
-                    new PkiRealmBootstrapCheck(settings, getSslService()),
-                    new TLSLicenseBootstrapCheck()));
+                new TokenSSLBootstrapCheck(),
+                new PkiRealmBootstrapCheck(settings, getSslService()),
+                new TLSLicenseBootstrapCheck(),
+                new PasswordHashingAlgorithmBootstrapCheck()));
             checks.addAll(InternalRealms.getBootstrapChecks(settings, env));
             this.bootstrapChecks = Collections.unmodifiableList(checks);
         } else {

+ 9 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordAction.java

@@ -12,9 +12,11 @@ import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.tasks.Task;
 import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.core.security.action.user.ChangePasswordAction;
 import org.elasticsearch.xpack.core.security.action.user.ChangePasswordRequest;
 import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.user.AnonymousUser;
 import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.XPackUser;
@@ -41,6 +43,13 @@ public class TransportChangePasswordAction extends HandledTransportAction<Change
             listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
             return;
         }
+        final String requestPwdHashAlgo = Hasher.resolveFromHash(request.passwordHash()).name();
+        final String configPwdHashAlgo = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings)).name();
+        if (requestPwdHashAlgo.equalsIgnoreCase(configPwdHashAlgo) == false) {
+            listener.onFailure(new IllegalArgumentException("incorrect password hashing algorithm [" + requestPwdHashAlgo + "] used while" +
+                " [" + configPwdHashAlgo + "] is configured."));
+            return;
+        }
         nativeUsersStore.changePassword(request, new ActionListener<Void>() {
             @Override
             public void onResponse(Void v) {

+ 14 - 6
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java

@@ -36,6 +36,7 @@ import org.elasticsearch.index.engine.DocumentMissingException;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.core.security.ScrollHelper;
 import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheRequest;
 import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheResponse;
@@ -78,10 +79,9 @@ public class NativeUsersStore extends AbstractComponent {
     public static final String INDEX_TYPE = "doc";
     static final String USER_DOC_TYPE = "user";
     public static final String RESERVED_USER_TYPE = "reserved-user";
-
-
-    private final Hasher hasher = Hasher.BCRYPT;
     private final Client client;
+    private final ReservedUserInfo disabledDefaultUserInfo;
+    private final ReservedUserInfo enabledDefaultUserInfo;
 
     private final SecurityIndexManager securityIndex;
 
@@ -89,6 +89,10 @@ public class NativeUsersStore extends AbstractComponent {
         super(settings);
         this.client = client;
         this.securityIndex = securityIndex;
+        final char[] emptyPasswordHash = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings)).
+            hash(new SecureString("".toCharArray()));
+        this.disabledDefaultUserInfo = new ReservedUserInfo(emptyPasswordHash, false, true);
+        this.enabledDefaultUserInfo = new ReservedUserInfo(emptyPasswordHash, true, true);
     }
 
     /**
@@ -485,7 +489,7 @@ public class NativeUsersStore extends AbstractComponent {
         getUserAndPassword(username, ActionListener.wrap((userAndPassword) -> {
             if (userAndPassword == null || userAndPassword.passwordHash() == null) {
                 listener.onResponse(AuthenticationResult.notHandled());
-            } else if (hasher.verify(password, userAndPassword.passwordHash())) {
+            } else if (userAndPassword.verifyPassword(password)) {
                 listener.onResponse(AuthenticationResult.success(userAndPassword.user()));
             } else {
                 listener.onResponse(AuthenticationResult.unsuccessful("Password authentication failed for " + username, null));
@@ -514,8 +518,7 @@ public class NativeUsersStore extends AbstractComponent {
                                         } else if (enabled == null) {
                                             listener.onFailure(new IllegalStateException("enabled must not be null!"));
                                         } else if (password.isEmpty()) {
-                                            listener.onResponse((enabled ? ReservedRealm.ENABLED_DEFAULT_USER_INFO : ReservedRealm
-                                                    .DISABLED_DEFAULT_USER_INFO).deepClone());
+                                            listener.onResponse((enabled ? enabledDefaultUserInfo : disabledDefaultUserInfo).deepClone());
                                         } else {
                                             listener.onResponse(new ReservedUserInfo(password.toCharArray(), enabled, false));
                                         }
@@ -651,16 +654,21 @@ public class NativeUsersStore extends AbstractComponent {
         public final char[] passwordHash;
         public final boolean enabled;
         public final boolean hasEmptyPassword;
+        private final Hasher hasher;
 
         ReservedUserInfo(char[] passwordHash, boolean enabled, boolean hasEmptyPassword) {
             this.passwordHash = passwordHash;
             this.enabled = enabled;
             this.hasEmptyPassword = hasEmptyPassword;
+            this.hasher = Hasher.resolveFromHash(this.passwordHash);
         }
 
         ReservedUserInfo deepClone() {
             return new ReservedUserInfo(Arrays.copyOf(passwordHash, passwordHash.length), enabled, hasEmptyPassword);
         }
 
+        boolean verifyPassword(SecureString data) {
+            return hasher.verify(data, this.passwordHash);
+        }
     }
 }

+ 15 - 12
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java

@@ -49,10 +49,6 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
     public static final String TYPE = "reserved";
 
     private final ReservedUserInfo bootstrapUserInfo;
-    static final char[] EMPTY_PASSWORD_HASH = Hasher.BCRYPT.hash(new SecureString("".toCharArray()));
-    static final ReservedUserInfo DISABLED_DEFAULT_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, false, true);
-    static final ReservedUserInfo ENABLED_DEFAULT_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, true, true);
-
     public static final Setting<Boolean> ACCEPT_DEFAULT_PASSWORD_SETTING = Setting.boolSetting(
             SecurityField.setting("authc.accept_default_password"), true, Setting.Property.NodeScope, Setting.Property.Filtered,
             Setting.Property.Deprecated);
@@ -64,6 +60,9 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
     private final boolean realmEnabled;
     private final boolean anonymousEnabled;
     private final SecurityIndexManager securityIndex;
+    private final Hasher reservedRealmHasher;
+    private final ReservedUserInfo disabledDefaultUserInfo;
+    private final ReservedUserInfo enabledDefaultUserInfo;
 
     public ReservedRealm(Environment env, Settings settings, NativeUsersStore nativeUsersStore, AnonymousUser anonymousUser,
                          SecurityIndexManager securityIndex, ThreadPool threadPool) {
@@ -73,9 +72,13 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
         this.anonymousUser = anonymousUser;
         this.anonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
         this.securityIndex = securityIndex;
-        final char[] hash = BOOTSTRAP_ELASTIC_PASSWORD.get(settings).length() == 0 ? EMPTY_PASSWORD_HASH :
-                Hasher.BCRYPT.hash(BOOTSTRAP_ELASTIC_PASSWORD.get(settings));
-        bootstrapUserInfo = new ReservedUserInfo(hash, true, hash == EMPTY_PASSWORD_HASH);
+        this.reservedRealmHasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings));
+        final char[] emptyPasswordHash = reservedRealmHasher.hash(new SecureString("".toCharArray()));
+        disabledDefaultUserInfo = new ReservedUserInfo(emptyPasswordHash, false, true);
+        enabledDefaultUserInfo = new ReservedUserInfo(emptyPasswordHash, true, true);
+        final char[] hash = BOOTSTRAP_ELASTIC_PASSWORD.get(settings).length() == 0 ? emptyPasswordHash :
+            reservedRealmHasher.hash(BOOTSTRAP_ELASTIC_PASSWORD.get(settings));
+        bootstrapUserInfo = new ReservedUserInfo(hash, true, hash == emptyPasswordHash);
     }
 
     @Override
@@ -91,15 +94,15 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
                     try {
                         if (userInfo.hasEmptyPassword) {
                             result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
-                        } else if (Hasher.BCRYPT.verify(token.credentials(), userInfo.passwordHash)) {
+                        } else if (userInfo.verifyPassword(token.credentials())) {
                             final User user = getUser(token.principal(), userInfo);
                             result = AuthenticationResult.success(user);
                         } else {
                             result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
                         }
                     } finally {
-                        assert userInfo.passwordHash != DISABLED_DEFAULT_USER_INFO.passwordHash : "default user info must be cloned";
-                        assert userInfo.passwordHash != ENABLED_DEFAULT_USER_INFO.passwordHash : "default user info must be cloned";
+                        assert userInfo.passwordHash != disabledDefaultUserInfo.passwordHash : "default user info must be cloned";
+                        assert userInfo.passwordHash != enabledDefaultUserInfo.passwordHash : "default user info must be cloned";
                         assert userInfo.passwordHash != bootstrapUserInfo.passwordHash : "bootstrap user info must be cloned";
                         Arrays.fill(userInfo.passwordHash, (char) 0);
                     }
@@ -190,7 +193,7 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
     private void getUserInfo(final String username, ActionListener<ReservedUserInfo> listener) {
         if (userIsDefinedForCurrentSecurityMapping(username) == false) {
             logger.debug("Marking user [{}] as disabled because the security mapping is not at the required version", username);
-            listener.onResponse(DISABLED_DEFAULT_USER_INFO.deepClone());
+            listener.onResponse(disabledDefaultUserInfo.deepClone());
         } else if (securityIndex.indexExists() == false) {
             listener.onResponse(getDefaultUserInfo(username));
         } else {
@@ -212,7 +215,7 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
         if (ElasticUser.NAME.equals(username)) {
             return bootstrapUserInfo.deepClone();
         } else {
-            return ENABLED_DEFAULT_USER_INFO.deepClone();
+            return enabledDefaultUserInfo.deepClone();
         }
     }
 

+ 8 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/UserAndPassword.java

@@ -5,6 +5,8 @@
  */
 package org.elasticsearch.xpack.security.authc.esnative;
 
+import org.elasticsearch.common.settings.SecureString;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.user.User;
 
 /**
@@ -20,10 +22,12 @@ class UserAndPassword {
 
     private final User user;
     private final char[] passwordHash;
+    private final Hasher hasher;
 
     UserAndPassword(User user, char[] passwordHash) {
         this.user = user;
         this.passwordHash = passwordHash;
+        this.hasher = Hasher.resolveFromHash(this.passwordHash);
     }
 
     public User user() {
@@ -34,6 +38,10 @@ class UserAndPassword {
         return this.passwordHash;
     }
 
+    boolean verifyPassword(SecureString data) {
+        return hasher.verify(data, this.passwordHash);
+    }
+
     @Override
     public boolean equals(Object o) {
         return false; // Don't use this for user comparison

+ 2 - 4
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java

@@ -46,10 +46,8 @@ public class FileUserPasswdStore {
     private final Logger logger;
 
     private final Path file;
-    private final Hasher hasher = Hasher.BCRYPT;
     private final Settings settings;
     private final CopyOnWriteArrayList<Runnable> listeners;
-
     private volatile Map<String, char[]> users;
 
     public FileUserPasswdStore(RealmConfig config, ResourceWatcherService watcherService) {
@@ -84,7 +82,7 @@ public class FileUserPasswdStore {
         if (hash == null) {
             return AuthenticationResult.notHandled();
         }
-        if (hasher.verify(password, hash) == false) {
+        if (Hasher.verifyHash(password, hash) == false) {
             return AuthenticationResult.unsuccessful("Password authentication failed for " + username, null);
         }
         return AuthenticationResult.success(user.get());
@@ -149,7 +147,7 @@ public class FileUserPasswdStore {
             // only trim the line because we have a format, our tool generates the formatted text and we shouldn't be lenient
             // and allow spaces in the format
             line = line.trim();
-            int i = line.indexOf(":");
+            int i = line.indexOf(':');
             if (i <= 0 || i == line.length() - 1) {
                 logger.error("invalid entry in users file [{}], line [{}]. skipping...", path.toAbsolutePath(), lineNr);
                 continue;

+ 6 - 5
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java

@@ -124,7 +124,7 @@ public class UsersTool extends LoggingAwareMultiCommand {
             if (users.containsKey(username)) {
                 throw new UserException(ExitCodes.CODE_ERROR, "User [" + username + "] already exists");
             }
-            Hasher hasher = Hasher.BCRYPT;
+            final Hasher hasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(env.settings()));
             users = new HashMap<>(users); // make modifiable
             users.put(username, hasher.hash(new SecureString(password)));
             FileUserPasswdStore.writeFile(users, passwordFile);
@@ -228,7 +228,8 @@ public class UsersTool extends LoggingAwareMultiCommand {
             if (users.containsKey(username) == false) {
                 throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
             }
-            users.put(username, Hasher.BCRYPT.hash(new SecureString(password)));
+            final Hasher hasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(env.settings()));
+            users.put(username, hasher.hash(new SecureString(password)));
             FileUserPasswdStore.writeFile(users, file);
 
             attributesChecker.check(terminal);
@@ -294,7 +295,7 @@ public class UsersTool extends LoggingAwareMultiCommand {
 
             Map<String, String[]> userRolesToWrite = new HashMap<>(userRoles.size());
             userRolesToWrite.putAll(userRoles);
-            if (roles.size() == 0) {
+            if (roles.isEmpty()) {
                 userRolesToWrite.remove(username);
             } else {
                 userRolesToWrite.put(username, new LinkedHashSet<>(roles).toArray(new String[]{}));
@@ -367,7 +368,7 @@ public class UsersTool extends LoggingAwareMultiCommand {
                     Path rolesFile = FileRolesStore.resolveFile(env).toAbsolutePath();
                     terminal.println("");
                     terminal.println(" [*]   Role is not in the [" + rolesFile.toAbsolutePath() + "] file. If the role has been created "
-                            + "using the API, please disregard this message.");
+                        + "using the API, please disregard this message.");
                 }
             } else {
                 terminal.println(String.format(Locale.ROOT, "%-15s: -", username));
@@ -401,7 +402,7 @@ public class UsersTool extends LoggingAwareMultiCommand {
                 Path rolesFile = FileRolesStore.resolveFile(env).toAbsolutePath();
                 terminal.println("");
                 terminal.println(" [*]   Role is not in the [" + rolesFile.toAbsolutePath() + "] file. If the role has been created "
-                        + "using the API, please disregard this message.");
+                    + "using the API, please disregard this message.");
             }
         }
     }

+ 4 - 6
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java

@@ -32,11 +32,11 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
 
     private final Cache<String, ListenableFuture<Tuple<AuthenticationResult, UserWithHash>>> cache;
     private final ThreadPool threadPool;
-    final Hasher hasher;
+    final Hasher cacheHasher;
 
     protected CachingUsernamePasswordRealm(String type, RealmConfig config, ThreadPool threadPool) {
         super(type, config);
-        hasher = Hasher.resolve(CachingUsernamePasswordRealmSettings.CACHE_HASH_ALGO_SETTING.get(config.settings()), Hasher.SSHA256);
+        cacheHasher = Hasher.resolve(CachingUsernamePasswordRealmSettings.CACHE_HASH_ALGO_SETTING.get(config.settings()));
         this.threadPool = threadPool;
         TimeValue ttl = CachingUsernamePasswordRealmSettings.CACHE_TTL_SETTING.get(config.settings());
         if (ttl.getNanos() > 0) {
@@ -102,7 +102,7 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
                     if (result.isAuthenticated()) {
                         final User user = result.getUser();
                         authenticatedUser.set(user);
-                        final UserWithHash userWithHash = new UserWithHash(user, token.credentials(), hasher);
+                        final UserWithHash userWithHash = new UserWithHash(user, token.credentials(), cacheHasher);
                         future.onResponse(new Tuple<>(result, userWithHash));
                     } else {
                         future.onResponse(new Tuple<>(result, null));
@@ -233,16 +233,14 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
     private static class UserWithHash {
         final User user;
         final char[] hash;
-        final Hasher hasher;
 
         UserWithHash(User user, SecureString password, Hasher hasher) {
             this.user = Objects.requireNonNull(user);
             this.hash = password == null ? null : hasher.hash(password);
-            this.hasher = hasher;
         }
 
         boolean verify(SecureString password) {
-            return hash != null && hasher.verify(password, hash);
+            return hash != null && Hasher.verifyHash(password, hash);
         }
     }
 }

+ 4 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestChangePasswordAction.java

@@ -15,8 +15,10 @@ import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.RestResponse;
 import org.elasticsearch.rest.RestStatus;
 import org.elasticsearch.rest.action.RestBuilderListener;
+import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.core.security.SecurityContext;
 import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.client.SecurityClient;
 import org.elasticsearch.xpack.core.security.rest.RestRequestFilter;
 import org.elasticsearch.xpack.core.security.user.User;
@@ -32,6 +34,7 @@ import static org.elasticsearch.rest.RestRequest.Method.PUT;
 public class RestChangePasswordAction extends SecurityBaseRestHandler implements RestRequestFilter {
 
     private final SecurityContext securityContext;
+    private final Hasher passwordHasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings));
 
     public RestChangePasswordAction(Settings settings, RestController controller, SecurityContext securityContext,
                                     XPackLicenseState licenseState) {
@@ -61,7 +64,7 @@ public class RestChangePasswordAction extends SecurityBaseRestHandler implements
         final String refresh = request.param("refresh");
         return channel ->
                 new SecurityClient(client)
-                        .prepareChangePassword(username, request.requiredContent(), request.getXContentType())
+                    .prepareChangePassword(username, request.requiredContent(), request.getXContentType(), passwordHasher)
                         .setRefreshPolicy(refresh)
                         .execute(new RestBuilderListener<ChangePasswordResponse>(channel) {
                             @Override

+ 5 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestPutUserAction.java

@@ -16,8 +16,10 @@ import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.RestResponse;
 import org.elasticsearch.rest.RestStatus;
 import org.elasticsearch.rest.action.RestBuilderListener;
+import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.core.security.action.user.PutUserRequestBuilder;
 import org.elasticsearch.xpack.core.security.action.user.PutUserResponse;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.client.SecurityClient;
 import org.elasticsearch.xpack.core.security.rest.RestRequestFilter;
 import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
@@ -34,6 +36,8 @@ import static org.elasticsearch.rest.RestRequest.Method.PUT;
  */
 public class RestPutUserAction extends SecurityBaseRestHandler implements RestRequestFilter {
 
+    private final Hasher passwordHasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings));
+
     public RestPutUserAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
         super(settings, licenseState);
         controller.registerHandler(POST, "/_xpack/security/user/{username}", this);
@@ -48,7 +52,7 @@ public class RestPutUserAction extends SecurityBaseRestHandler implements RestRe
     @Override
     public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
         PutUserRequestBuilder requestBuilder = new SecurityClient(client)
-                .preparePutUser(request.param("username"), request.requiredContent(), request.getXContentType())
+            .preparePutUser(request.param("username"), request.requiredContent(), request.getXContentType(), passwordHasher)
                 .setRefreshPolicy(request.param("refresh"));
 
         return channel -> requestBuilder.execute(new RestBuilderListener<PutUserResponse>(channel) {

+ 0 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java

@@ -15,7 +15,6 @@ import org.elasticsearch.client.Response;
 import org.elasticsearch.client.ResponseException;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.test.SecuritySingleNodeTestCase;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
 
 import java.io.IOException;
@@ -33,8 +32,6 @@ import static org.hamcrest.Matchers.not;
  */
 public abstract class AbstractPrivilegeTestCase extends SecuritySingleNodeTestCase {
 
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("passwd".toCharArray())));
-
     protected void assertAccessIsAllowed(String user, String method, String uri, String body,
                                          Map<String, String> params) throws IOException {
         Response response = getRestClient().performRequest(method, uri, params, entityOrNull(body),

+ 3 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java

@@ -19,7 +19,6 @@ import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheRequest
 import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheResponse;
 import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
 import org.elasticsearch.xpack.core.security.authc.Realm;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
 import org.elasticsearch.xpack.core.security.client.SecurityClient;
 import org.elasticsearch.xpack.core.security.user.User;
@@ -42,7 +41,6 @@ import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.sameInstance;
 
 public class ClearRealmsCacheTests extends SecurityIntegTestCase {
-    private static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("passwd".toCharArray())));
 
     private static String[] usernames;
 
@@ -186,8 +184,10 @@ public class ClearRealmsCacheTests extends SecurityIntegTestCase {
     @Override
     protected String configUsers() {
         StringBuilder builder = new StringBuilder(SecuritySettingsSource.CONFIG_STANDARD_USER);
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString
+            ("passwd".toCharArray())));
         for (String username : usernames) {
-            builder.append(username).append(":").append(USERS_PASSWD_HASHED).append("\n");
+            builder.append(username).append(":").append(usersPasswdHashed).append("\n");
         }
         return builder.toString();
     }

+ 9 - 6
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java

@@ -8,7 +8,9 @@ package org.elasticsearch.integration;
 import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusResponse;
 import org.elasticsearch.cluster.SnapshotsInProgress;
 import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
@@ -33,11 +35,6 @@ public class ClusterPrivilegeTests extends AbstractPrivilegeTestCase {
                     "    - names: 'someindex'\n" +
                     "      privileges: [ all ]\n";
 
-    private static final String USERS =
-                    "user_a:" + USERS_PASSWD_HASHED + "\n" +
-                    "user_b:" + USERS_PASSWD_HASHED + "\n" +
-                    "user_c:" + USERS_PASSWD_HASHED + "\n";
-
     private static final String USERS_ROLES =
                     "role_a:user_a\n" +
                     "role_b:user_b\n" +
@@ -74,7 +71,13 @@ public class ClusterPrivilegeTests extends AbstractPrivilegeTestCase {
 
     @Override
     protected String configUsers() {
-        return super.configUsers() + USERS;
+        final String usersPasswdHashed = new String(Hasher.resolve(
+            randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(new SecureString("passwd".toCharArray())));
+        return super.configUsers() +
+            "user_a:" + usersPasswdHashed + "\n" +
+            "user_b:" + usersPasswdHashed + "\n" +
+            "user_c:" + usersPasswdHashed + "\n";
+
     }
 
     @Override

+ 3 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DateMathExpressionIntegTests.java

@@ -21,7 +21,6 @@ import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.test.SecurityIntegTestCase;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 
 import java.util.Collections;
 
@@ -34,12 +33,13 @@ import static org.hamcrest.Matchers.is;
 public class DateMathExpressionIntegTests extends SecurityIntegTestCase {
 
     protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(USERS_PASSWD));
 
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
+
         return super.configUsers() +
-                "user1:" + USERS_PASSWD_HASHED + "\n";
+            "user1:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 7 - 7
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java

@@ -22,7 +22,6 @@ import org.elasticsearch.indices.IndicesModule;
 import org.elasticsearch.search.sort.SortOrder;
 import org.elasticsearch.test.SecurityIntegTestCase;
 import org.elasticsearch.xpack.core.XPackSettings;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -40,16 +39,17 @@ import static org.hamcrest.Matchers.equalTo;
 public class DocumentAndFieldLevelSecurityTests extends SecurityIntegTestCase {
 
     protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(USERS_PASSWD));
 
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
+
         return super.configUsers() +
-                "user1:" + USERS_PASSWD_HASHED + "\n" +
-                "user2:" + USERS_PASSWD_HASHED + "\n" +
-                "user3:" + USERS_PASSWD_HASHED + "\n" +
-                "user4:" + USERS_PASSWD_HASHED + "\n" +
-                "user5:" + USERS_PASSWD_HASHED + "\n";
+            "user1:" + usersPasswdHashed + "\n" +
+            "user2:" + usersPasswdHashed + "\n" +
+            "user3:" + usersPasswdHashed + "\n" +
+            "user4:" + usersPasswdHashed + "\n" +
+            "user5:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 3 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityRandomTests.java

@@ -12,7 +12,6 @@ import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.xpack.core.XPackSettings;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.test.SecurityIntegTestCase;
 
 import java.util.ArrayList;
@@ -27,7 +26,6 @@ import static org.hamcrest.Matchers.equalTo;
 public class DocumentLevelSecurityRandomTests extends SecurityIntegTestCase {
 
     protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray())));
 
     // can't add a second test method, because each test run creates a new instance of this class and that will will result
     // in a new random value:
@@ -35,9 +33,11 @@ public class DocumentLevelSecurityRandomTests extends SecurityIntegTestCase {
 
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
+
         StringBuilder builder = new StringBuilder(super.configUsers());
         for (int i = 1; i <= numberOfRoles; i++) {
-            builder.append("user").append(i).append(':').append(USERS_PASSWD_HASHED).append('\n');
+            builder.append("user").append(i).append(':').append(usersPasswdHashed).append('\n');
         }
         return builder.toString();
     }

+ 4 - 5
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java

@@ -52,7 +52,6 @@ import org.elasticsearch.test.InternalSettingsPlugin;
 import org.elasticsearch.test.SecurityIntegTestCase;
 import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.security.LocalStateSecurity;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -81,7 +80,6 @@ import static org.hamcrest.Matchers.notNullValue;
 public class DocumentLevelSecurityTests extends SecurityIntegTestCase {
 
     protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(USERS_PASSWD));
 
     @Override
     protected Collection<Class<? extends Plugin>> nodePlugins() {
@@ -95,10 +93,11 @@ public class DocumentLevelSecurityTests extends SecurityIntegTestCase {
 
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
         return super.configUsers() +
-                "user1:" + USERS_PASSWD_HASHED + "\n" +
-                "user2:" + USERS_PASSWD_HASHED + "\n" +
-                "user3:" + USERS_PASSWD_HASHED + "\n" ;
+            "user1:" + usersPasswdHashed + "\n" +
+            "user2:" + usersPasswdHashed + "\n" +
+            "user3:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 6 - 6
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java

@@ -12,7 +12,6 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.search.sort.SortOrder;
 import org.elasticsearch.xpack.core.XPackSettings;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.test.SecurityIntegTestCase;
 
 import java.util.ArrayList;
@@ -34,18 +33,19 @@ import static org.hamcrest.Matchers.equalTo;
 public class FieldLevelSecurityRandomTests extends SecurityIntegTestCase {
 
     protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray())));
 
     private static Set<String> allowedFields;
     private static Set<String> disAllowedFields;
 
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
+
         return super.configUsers() +
-                "user1:" + USERS_PASSWD_HASHED + "\n" +
-                "user2:" + USERS_PASSWD_HASHED + "\n" +
-                "user3:" + USERS_PASSWD_HASHED + "\n" +
-                "user4:" + USERS_PASSWD_HASHED + "\n" ;
+            "user1:" + usersPasswdHashed + "\n" +
+            "user2:" + usersPasswdHashed + "\n" +
+            "user3:" + usersPasswdHashed + "\n" +
+            "user4:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 9 - 10
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java

@@ -38,7 +38,6 @@ import org.elasticsearch.test.InternalSettingsPlugin;
 import org.elasticsearch.test.SecurityIntegTestCase;
 import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.security.LocalStateSecurity;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -67,7 +66,6 @@ import static org.hamcrest.Matchers.nullValue;
 public class FieldLevelSecurityTests extends SecurityIntegTestCase {
 
     protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray())));
 
     @Override
     protected Collection<Class<? extends Plugin>> nodePlugins() {
@@ -82,15 +80,16 @@ public class FieldLevelSecurityTests extends SecurityIntegTestCase {
 
     @Override
     protected String configUsers() {
+        final String usersPasswHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
         return super.configUsers() +
-                "user1:" + USERS_PASSWD_HASHED + "\n" +
-                "user2:" + USERS_PASSWD_HASHED + "\n" +
-                "user3:" + USERS_PASSWD_HASHED + "\n" +
-                "user4:" + USERS_PASSWD_HASHED + "\n" +
-                "user5:" + USERS_PASSWD_HASHED + "\n" +
-                "user6:" + USERS_PASSWD_HASHED + "\n" +
-                "user7:" + USERS_PASSWD_HASHED + "\n" +
-                "user8:" + USERS_PASSWD_HASHED + "\n";
+            "user1:" + usersPasswHashed + "\n" +
+            "user2:" + usersPasswHashed + "\n" +
+            "user3:" + usersPasswHashed + "\n" +
+            "user4:" + usersPasswHashed + "\n" +
+            "user5:" + usersPasswHashed + "\n" +
+            "user6:" + usersPasswHashed + "\n" +
+            "user7:" + usersPasswHashed + "\n" +
+            "user8:" + usersPasswHashed + "\n";
     }
 
     @Override

+ 19 - 17
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java

@@ -8,6 +8,7 @@ package org.elasticsearch.integration;
 import org.apache.http.message.BasicHeader;
 import org.elasticsearch.client.ResponseException;
 import org.elasticsearch.common.settings.SecureString;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
 import org.junit.Before;
 
@@ -84,22 +85,6 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
                     "      privileges: [ index ]\n" +
                     "\n";
 
-    private static final String USERS =
-            "admin:" + USERS_PASSWD_HASHED + "\n" +
-            "u1:" + USERS_PASSWD_HASHED + "\n" +
-            "u2:" + USERS_PASSWD_HASHED + "\n" +
-            "u3:" + USERS_PASSWD_HASHED + "\n" +
-            "u4:" + USERS_PASSWD_HASHED + "\n" +
-            "u5:" + USERS_PASSWD_HASHED + "\n" +
-            "u6:" + USERS_PASSWD_HASHED + "\n" +
-            "u7:" + USERS_PASSWD_HASHED + "\n"+
-            "u8:" + USERS_PASSWD_HASHED + "\n"+
-            "u9:" + USERS_PASSWD_HASHED + "\n" +
-            "u11:" + USERS_PASSWD_HASHED + "\n" +
-            "u12:" + USERS_PASSWD_HASHED + "\n" +
-            "u13:" + USERS_PASSWD_HASHED + "\n" +
-            "u14:" + USERS_PASSWD_HASHED + "\n";
-
     private static final String USERS_ROLES =
             "all_indices_role:admin,u8\n" +
             "all_cluster_role:admin\n" +
@@ -129,7 +114,24 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
 
     @Override
     protected String configUsers() {
-        return super.configUsers() + USERS;
+        final String usersPasswdHashed = new String(Hasher.resolve(
+            randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(new SecureString("passwd".toCharArray())));
+
+        return super.configUsers() +
+            "admin:" + usersPasswdHashed + "\n" +
+            "u1:" + usersPasswdHashed + "\n" +
+            "u2:" + usersPasswdHashed + "\n" +
+            "u3:" + usersPasswdHashed + "\n" +
+            "u4:" + usersPasswdHashed + "\n" +
+            "u5:" + usersPasswdHashed + "\n" +
+            "u6:" + usersPasswdHashed + "\n" +
+            "u7:" + usersPasswdHashed + "\n" +
+            "u8:" + usersPasswdHashed + "\n" +
+            "u9:" + usersPasswdHashed + "\n" +
+            "u11:" + usersPasswdHashed + "\n" +
+            "u12:" + usersPasswdHashed + "\n" +
+            "u13:" + usersPasswdHashed + "\n" +
+            "u14:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 2 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java

@@ -10,7 +10,6 @@ import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.xpack.core.XPackSettings;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.test.SecurityIntegTestCase;
 
 import java.util.Collections;
@@ -24,12 +23,12 @@ import static org.hamcrest.Matchers.equalTo;
 public class IndicesPermissionsWithAliasesWildcardsAndRegexsTests extends SecurityIntegTestCase {
 
     protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray())));
 
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
         return super.configUsers() +
-                "user1:" + USERS_PASSWD_HASHED + "\n";
+            "user1:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 2 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/KibanaUserRoleIntegTests.java

@@ -21,7 +21,6 @@ import org.elasticsearch.common.collect.ImmutableOpenMap;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.test.SecurityIntegTestCase;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
 
 import java.util.Locale;
@@ -40,7 +39,6 @@ import static org.hamcrest.Matchers.notNullValue;
 public class KibanaUserRoleIntegTests extends SecurityIntegTestCase {
 
     protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray())));
 
     @Override
     public String configRoles() {
@@ -55,8 +53,9 @@ public class KibanaUserRoleIntegTests extends SecurityIntegTestCase {
 
     @Override
     public String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
         return super.configUsers() +
-                "kibana_user:" + USERS_PASSWD_HASHED;
+            "kibana_user:" + usersPasswdHashed;
     }
 
     @Override

+ 11 - 11
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/MultipleIndicesPermissionsTests.java

@@ -13,7 +13,6 @@ import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.rest.RestStatus;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.test.SecurityIntegTestCase;
 import org.elasticsearch.test.SecuritySettingsSource;
 
@@ -28,8 +27,8 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFa
 import static org.hamcrest.Matchers.is;
 
 public class MultipleIndicesPermissionsTests extends SecurityIntegTestCase {
-    protected static final SecureString PASSWD = new SecureString("passwd".toCharArray());
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(PASSWD));
+
+    protected static final SecureString USERS_PASSWD = new SecureString("passwd".toCharArray());
 
     @Override
     protected String configRoles() {
@@ -58,9 +57,10 @@ public class MultipleIndicesPermissionsTests extends SecurityIntegTestCase {
 
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
         return SecuritySettingsSource.CONFIG_STANDARD_USER +
-                "user_a:" + USERS_PASSWD_HASHED + "\n" +
-                "user_ab:" + USERS_PASSWD_HASHED + "\n";
+            "user_a:" + usersPasswdHashed + "\n" +
+            "user_ab:" + usersPasswdHashed + "\n";
     }
 
     @Override
@@ -145,7 +145,7 @@ public class MultipleIndicesPermissionsTests extends SecurityIntegTestCase {
         Client client = internalCluster().transportClient();
 
         SearchResponse response = client
-                .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", PASSWD)))
+            .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", USERS_PASSWD)))
                 .prepareSearch("a")
                 .get();
         assertNoFailures(response);
@@ -156,7 +156,7 @@ public class MultipleIndicesPermissionsTests extends SecurityIntegTestCase {
                 new String[] { "*" } :
                 new String[] {};
         response = client
-                .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", PASSWD)))
+            .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", USERS_PASSWD)))
                 .prepareSearch(indices)
                 .get();
         assertNoFailures(response);
@@ -165,7 +165,7 @@ public class MultipleIndicesPermissionsTests extends SecurityIntegTestCase {
         try {
             indices = randomBoolean() ? new String[] { "a", "b" } : new String[] { "b", "a" };
             client
-                    .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", PASSWD)))
+                .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_a", USERS_PASSWD)))
                     .prepareSearch(indices)
                     .get();
             fail("expected an authorization excpetion when trying to search on multiple indices where there are no search permissions on " +
@@ -175,14 +175,14 @@ public class MultipleIndicesPermissionsTests extends SecurityIntegTestCase {
             assertThat(e.status(), is(RestStatus.FORBIDDEN));
         }
 
-        response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", PASSWD)))
+        response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", USERS_PASSWD)))
                 .prepareSearch("b")
                 .get();
         assertNoFailures(response);
         assertHitCount(response, 1);
 
         indices = randomBoolean() ? new String[] { "a", "b" } : new String[] { "b", "a" };
-        response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", PASSWD)))
+        response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", USERS_PASSWD)))
                 .prepareSearch(indices)
                 .get();
         assertNoFailures(response);
@@ -192,7 +192,7 @@ public class MultipleIndicesPermissionsTests extends SecurityIntegTestCase {
                 new String[] { "_all"} : randomBoolean() ?
                 new String[] { "*" } :
                 new String[] {};
-        response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", PASSWD)))
+        response = client.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user_ab", USERS_PASSWD)))
                 .prepareSearch(indices)
                 .get();
         assertNoFailures(response);

+ 4 - 5
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/PermissionPrecedenceTests.java

@@ -13,7 +13,6 @@ import org.elasticsearch.client.Client;
 import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.test.SecurityIntegTestCase;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
 
 import java.util.Collections;
@@ -33,7 +32,6 @@ import static org.hamcrest.Matchers.hasSize;
  * index template actions.
  */
 public class PermissionPrecedenceTests extends SecurityIntegTestCase {
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("test123".toCharArray())));
 
     @Override
     protected String configRoles() {
@@ -51,9 +49,10 @@ public class PermissionPrecedenceTests extends SecurityIntegTestCase {
 
     @Override
     protected String configUsers() {
-        return "admin:" + USERS_PASSWD_HASHED + "\n" +
-                "client:" + USERS_PASSWD_HASHED + "\n" +
-                "user:" + USERS_PASSWD_HASHED + "\n";
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString("test123".toCharArray())));
+        return "admin:" + usersPasswdHashed + "\n" +
+            "client:" + usersPasswdHashed + "\n" +
+            "user:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 3 - 4
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/SecurityClearScrollTests.java

@@ -15,7 +15,6 @@ import org.elasticsearch.action.search.SearchPhaseExecutionException;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.xpack.core.security.SecurityField;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.test.SecurityIntegTestCase;
 import org.junit.After;
 import org.junit.Before;
@@ -33,15 +32,15 @@ import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.is;
 
 public class SecurityClearScrollTests extends SecurityIntegTestCase {
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("change_me".toCharArray())));
 
     private List<String> scrollIds;
 
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString("change_me".toCharArray())));
         return super.configUsers() +
-            "allowed_user:" + USERS_PASSWD_HASHED + "\n" +
-            "denied_user:" + USERS_PASSWD_HASHED + "\n" ;
+            "allowed_user:" + usersPasswdHashed + "\n" +
+            "denied_user:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 3 - 6
x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java

@@ -86,11 +86,6 @@ public class LicensingTests extends SecurityIntegTestCase {
                     "    - names: 'b'\n" +
                     "      privileges: [all]\n";
 
-    public static final String USERS =
-            SecuritySettingsSource.CONFIG_STANDARD_USER +
-                    "user_a:{plain}passwd\n" +
-                    "user_b:{plain}passwd\n";
-
     public static final String USERS_ROLES =
             SecuritySettingsSource.CONFIG_STANDARD_USER_ROLES +
                     "role_a:user_a,user_b\n" +
@@ -103,7 +98,9 @@ public class LicensingTests extends SecurityIntegTestCase {
 
     @Override
     protected String configUsers() {
-        return USERS;
+        return SecuritySettingsSource.CONFIG_STANDARD_USER +
+            "user_a:{plain}passwd\n" +
+            "user_b:{plain}passwd\n";
     }
 
     @Override

+ 5 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java

@@ -39,6 +39,7 @@ import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.xpack.core.XPackClient;
 import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.core.security.SecurityField;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
 import org.elasticsearch.xpack.core.security.client.SecurityClient;
 import org.elasticsearch.xpack.security.LocalStateSecurity;
@@ -88,7 +89,6 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase {
      */
     private static CustomSecuritySettingsSource customSecuritySettingsSource = null;
 
-
     @BeforeClass
     public static void generateBootstrapPassword() {
         BOOTSTRAP_PASSWORD = TEST_PASSWORD_SECURE_STRING.clone();
@@ -522,4 +522,8 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase {
     protected boolean isTransportSSLEnabled() {
         return customSecuritySettingsSource.isSslEnabled();
     }
+
+    protected static Hasher getFastStoredHashAlgoForTests() {
+        return Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9"));
+    }
 }

+ 4 - 2
x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java

@@ -42,6 +42,7 @@ import java.util.function.Consumer;
 
 import static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean;
 import static org.apache.lucene.util.LuceneTestCase.createTempFile;
+import static org.elasticsearch.test.ESTestCase.randomFrom;
 import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
 import static org.elasticsearch.xpack.security.test.SecurityTestUtils.writeFile;
 
@@ -57,7 +58,8 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas
 
     public static final String TEST_USER_NAME = "test_user";
     public static final String TEST_PASSWORD_HASHED =
-        new String(Hasher.BCRYPT.hash(new SecureString(SecuritySettingsSourceField.TEST_PASSWORD.toCharArray())));
+        new String(Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt9", "bcrypt8", "bcrypt")).
+            hash(new SecureString(SecuritySettingsSourceField.TEST_PASSWORD.toCharArray())));
     public static final String TEST_ROLE = "user";
     public static final String TEST_SUPERUSER = "test_superuser";
 
@@ -133,7 +135,7 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas
                 .put("xpack.security.authc.realms.file.type", FileRealmSettings.TYPE)
                 .put("xpack.security.authc.realms.file.order", 0)
                 .put("xpack.security.authc.realms.index.type", NativeRealmSettings.TYPE)
-                .put("xpack.security.authc.realms.index.order", "1");
+            .put("xpack.security.authc.realms.index.order", "1");
         addNodeSSLSettings(builder);
         return builder.build();
     }

+ 44 - 0
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/PasswordHashingAlgorithmBootstrapCheckTests.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.security;
+
+import org.elasticsearch.bootstrap.BootstrapContext;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.core.XPackSettings;
+
+import javax.crypto.SecretKeyFactory;
+import java.security.NoSuchAlgorithmException;
+
+public class PasswordHashingAlgorithmBootstrapCheckTests extends ESTestCase {
+
+    public void testPasswordHashingAlgorithmBootstrapCheck() {
+        Settings settings = Settings.EMPTY;
+        assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure());
+        // The following two will always pass because for now we only test in environments where PBKDF2WithHMACSHA512 is supported
+        assertTrue(isSecretkeyFactoryAlgoAvailable("PBKDF2WithHMACSHA512"));
+        settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2_10000").build();
+        assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure());
+
+        settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2").build();
+        assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure());
+
+        settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "BCRYPT").build();
+        assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure());
+
+        settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "BCRYPT11").build();
+        assertFalse(new PasswordHashingAlgorithmBootstrapCheck().check(new BootstrapContext(settings, null)).isFailure());
+    }
+
+    private boolean isSecretkeyFactoryAlgoAvailable(String algorithmId) {
+        try {
+            SecretKeyFactory.getInstance(algorithmId);
+            return true;
+        } catch (NoSuchAlgorithmException e) {
+            return false;
+        }
+    }
+}

+ 7 - 6
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java

@@ -12,6 +12,7 @@ import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.xpack.core.security.action.user.PutUserRequest;
 import org.elasticsearch.xpack.core.security.action.user.PutUserRequestBuilder;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -35,7 +36,7 @@ public class PutUserRequestBuilderTests extends ESTestCase {
                 "}";
 
         PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class));
-        builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON);
+        builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT);
 
         PutUserRequest request = builder.request();
         assertThat(request.username(), is("kibana4"));
@@ -55,7 +56,7 @@ public class PutUserRequestBuilderTests extends ESTestCase {
                 "}";
 
         PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class));
-        builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON);
+        builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT);
 
         PutUserRequest request = builder.request();
         assertThat(request.username(), is("kibana4"));
@@ -76,7 +77,7 @@ public class PutUserRequestBuilderTests extends ESTestCase {
                 "}";
 
         PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class));
-        builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON);
+        builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT);
 
         PutUserRequest request = builder.request();
         assertThat(request.username(), is("kibana4"));
@@ -98,7 +99,7 @@ public class PutUserRequestBuilderTests extends ESTestCase {
 
         PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class));
         ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
-                () -> builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON));
+            () -> builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT));
         assertThat(e.getMessage(), containsString("expected field [full_name] to be of type string"));
     }
 
@@ -114,7 +115,7 @@ public class PutUserRequestBuilderTests extends ESTestCase {
 
         PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class));
         ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
-                () -> builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON));
+            () -> builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT));
         assertThat(e.getMessage(), containsString("expected field [email] to be of type string"));
     }
 
@@ -131,7 +132,7 @@ public class PutUserRequestBuilderTests extends ESTestCase {
 
         PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class));
         PutUserRequest request =
-                builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON).request();
+            builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON, Hasher.BCRYPT).request();
         assertFalse(request.enabled());
     }
 }

+ 53 - 11
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportChangePasswordActionTests.java

@@ -13,6 +13,7 @@ import org.elasticsearch.tasks.Task;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.SecuritySettingsSourceField;
 import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.core.security.action.user.ChangePasswordRequest;
 import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse;
 import org.elasticsearch.xpack.core.security.authc.support.Hasher;
@@ -52,11 +53,12 @@ public class TransportChangePasswordActionTests extends ESTestCase {
         TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR,
                 x -> null, null, Collections.emptySet());
         TransportChangePasswordAction action = new TransportChangePasswordAction(settings, transportService,
-                mock(ActionFilters.class), usersStore);
+            mock(ActionFilters.class), usersStore);
 
         ChangePasswordRequest request = new ChangePasswordRequest();
         request.username(anonymousUser.principal());
-        request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
+        request.passwordHash(Hasher.resolve(
+            randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
 
         final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
         final AtomicReference<ChangePasswordResponse> responseRef = new AtomicReference<>();
@@ -83,11 +85,12 @@ public class TransportChangePasswordActionTests extends ESTestCase {
         TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR,
                 x -> null, null, Collections.emptySet());
         TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, transportService,
-                mock(ActionFilters.class), usersStore);
+            mock(ActionFilters.class), usersStore);
 
         ChangePasswordRequest request = new ChangePasswordRequest();
         request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
-        request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
+        request.passwordHash(Hasher.resolve(
+            randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
 
         final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
         final AtomicReference<ChangePasswordResponse> responseRef = new AtomicReference<>();
@@ -110,11 +113,13 @@ public class TransportChangePasswordActionTests extends ESTestCase {
     }
 
     public void testValidUser() {
+        final String hashingAlgorithm = randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9");
         final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
         NativeUsersStore usersStore = mock(NativeUsersStore.class);
+        final Hasher hasher = Hasher.resolve(hashingAlgorithm);
         ChangePasswordRequest request = new ChangePasswordRequest();
         request.username(user.principal());
-        request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
+        request.passwordHash(hasher.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
             assert args.length == 2;
@@ -124,9 +129,10 @@ public class TransportChangePasswordActionTests extends ESTestCase {
         }).when(usersStore).changePassword(eq(request), any(ActionListener.class));
         TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR,
                 x -> null, null, Collections.emptySet());
-        TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, transportService,
-                mock(ActionFilters.class), usersStore);
-
+        Settings passwordHashingSettings = Settings.builder().
+            put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), hashingAlgorithm).build();
+        TransportChangePasswordAction action = new TransportChangePasswordAction(passwordHashingSettings, transportService,
+            mock(ActionFilters.class), usersStore);
         final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
         final AtomicReference<ChangePasswordResponse> responseRef = new AtomicReference<>();
         action.doExecute(mock(Task.class), request, new ActionListener<ChangePasswordResponse>() {
@@ -147,12 +153,46 @@ public class TransportChangePasswordActionTests extends ESTestCase {
         verify(usersStore, times(1)).changePassword(eq(request), any(ActionListener.class));
     }
 
+    public void testIncorrectPasswordHashingAlgorithm() {
+        final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
+        final Hasher hasher = Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt9", "bcrypt5"));
+        NativeUsersStore usersStore = mock(NativeUsersStore.class);
+        ChangePasswordRequest request = new ChangePasswordRequest();
+        request.username(user.principal());
+        request.passwordHash(hasher.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
+        final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
+        final AtomicReference<ChangePasswordResponse> responseRef = new AtomicReference<>();
+        TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR,
+            x -> null, null, Collections.emptySet());
+        Settings passwordHashingSettings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(),
+            randomFrom("pbkdf2_50000", "pbkdf2_10000", "bcrypt11", "bcrypt8", "bcrypt")).build();
+        TransportChangePasswordAction action = new TransportChangePasswordAction(passwordHashingSettings, transportService,
+            mock(ActionFilters.class), usersStore);
+        action.doExecute(mock(Task.class), request, new ActionListener<ChangePasswordResponse>() {
+            @Override
+            public void onResponse(ChangePasswordResponse changePasswordResponse) {
+                responseRef.set(changePasswordResponse);
+            }
+
+            @Override
+            public void onFailure(Exception e) {
+                throwableRef.set(e);
+            }
+        });
+
+        assertThat(responseRef.get(), is(nullValue()));
+        assertThat(throwableRef.get(), instanceOf(IllegalArgumentException.class));
+        assertThat(throwableRef.get().getMessage(), containsString("incorrect password hashing algorithm"));
+        verifyZeroInteractions(usersStore);
+    }
+
     public void testException() {
+        final String hashingAlgorithm = randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9");
         final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
         NativeUsersStore usersStore = mock(NativeUsersStore.class);
         ChangePasswordRequest request = new ChangePasswordRequest();
         request.username(user.principal());
-        request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
+        request.passwordHash(Hasher.resolve(hashingAlgorithm).hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
         final Exception e = randomFrom(new ElasticsearchSecurityException(""), new IllegalStateException(), new RuntimeException());
         doAnswer(new Answer() {
             public Void answer(InvocationOnMock invocation) {
@@ -165,8 +205,10 @@ public class TransportChangePasswordActionTests extends ESTestCase {
         }).when(usersStore).changePassword(eq(request), any(ActionListener.class));
         TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR,
                 x -> null, null, Collections.emptySet());
-        TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, transportService,
-                mock(ActionFilters.class), usersStore);
+        Settings passwordHashingSettings = Settings.builder().
+            put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), hashingAlgorithm).build();
+        TransportChangePasswordAction action = new TransportChangePasswordAction(passwordHashingSettings, transportService,
+            mock(ActionFilters.class), usersStore);
 
         final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
         final AtomicReference<ChangePasswordResponse> responseRef = new AtomicReference<>();

+ 2 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportPutUserActionTests.java

@@ -165,7 +165,8 @@ public class TransportPutUserActionTests extends ESTestCase {
         final PutUserRequest request = new PutUserRequest();
         request.username(user.principal());
         if (isCreate) {
-            request.passwordHash(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
+            request.passwordHash(Hasher.resolve(
+                randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
         }
         final boolean created = isCreate ? randomBoolean() : false; // updates should always return false for create
         doAnswer(new Answer() {

+ 4 - 5
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java

@@ -27,8 +27,7 @@ import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.notNullValue;
 
 public class RealmSettingsTests extends ESTestCase {
-
-    private static final List<String> HASH_ALGOS = Arrays.stream(Hasher.values()).map(Hasher::name).collect(Collectors.toList());
+    private static final List<String> CACHE_HASHING_ALGOS = Arrays.stream(Hasher.values()).map(Hasher::name).collect(Collectors.toList());
 
     public void testRealmWithoutTypeDoesNotValidate() throws Exception {
         final Settings.Builder builder = baseSettings("x", false);
@@ -260,8 +259,8 @@ public class RealmSettingsTests extends ESTestCase {
                 .put("enabled", true);
         if (withCacheSettings) {
             builder.put("cache.ttl", randomPositiveTimeValue())
-                    .put("cache.max_users", randomIntBetween(1_000, 1_000_000))
-                    .put("cache.hash_algo", randomFrom(HASH_ALGOS));
+                .put("cache.max_users", randomIntBetween(1_000, 1_000_000))
+                .put("cache.hash_algo", randomFrom(CACHE_HASHING_ALGOS));
         }
         return builder;
     }
@@ -330,4 +329,4 @@ public class RealmSettingsTests extends ESTestCase {
         assertThat(list, hasSize(1));
         return list.get(0);
     }
-}
+}

+ 1 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java

@@ -77,7 +77,7 @@ public class ESNativeMigrateToolTests extends NativeRealmIntegTestCase {
         Set<String> addedUsers = new HashSet(numToAdd);
         for (int i = 0; i < numToAdd; i++) {
             String uname = randomAlphaOfLength(5);
-            c.preparePutUser(uname, "s3kirt".toCharArray(), "role1", "user").get();
+            c.preparePutUser(uname, "s3kirt".toCharArray(), getFastStoredHashAlgoForTests(), "role1", "user").get();
             addedUsers.add(uname);
         }
         logger.error("--> waiting for .security index");

+ 47 - 34
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java

@@ -111,7 +111,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
     public void testDeletingNonexistingUserAndRole() throws Exception {
         SecurityClient c = securityClient();
         // first create the index so it exists
-        c.preparePutUser("joe", "s3kirt".toCharArray(), "role1", "user").get();
+        c.preparePutUser("joe", "s3kirt".toCharArray(), getFastStoredHashAlgoForTests(), "role1", "user").get();
         DeleteUserResponse resp = c.prepareDeleteUser("missing").get();
         assertFalse("user shouldn't be found", resp.found());
         DeleteRoleResponse resp2 = c.prepareDeleteRole("role").get();
@@ -131,7 +131,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         final List<User> existingUsers = Arrays.asList(c.prepareGetUsers().get().users());
         final int existing = existingUsers.size();
         logger.error("--> creating user");
-        c.preparePutUser("joe", "s3kirt".toCharArray(), "role1", "user").get();
+        c.preparePutUser("joe", "s3kirt".toCharArray(), getFastStoredHashAlgoForTests(), "role1", "user").get();
         logger.error("--> waiting for .security index");
         ensureGreen(SECURITY_INDEX_NAME);
         logger.info("--> retrieving user");
@@ -142,8 +142,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         assertArrayEquals(joe.roles(), new String[]{"role1", "user"});
 
         logger.info("--> adding two more users");
-        c.preparePutUser("joe2", "s3kirt2".toCharArray(), "role2", "user").get();
-        c.preparePutUser("joe3", "s3kirt3".toCharArray(), "role3", "user").get();
+        c.preparePutUser("joe2", "s3kirt2".toCharArray(), getFastStoredHashAlgoForTests(), "role2", "user").get();
+        c.preparePutUser("joe3", "s3kirt3".toCharArray(), getFastStoredHashAlgoForTests(), "role3", "user").get();
         GetUsersResponse allUsersResp = c.prepareGetUsers().get();
         assertTrue("users should exist", allUsersResp.hasUsers());
         assertEquals("should be " + (3 + existing) + " users total", 3 + existing, allUsersResp.users().length);
@@ -237,7 +237,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
                         new BytesArray("{\"match_all\": {}}"))
                 .get();
         logger.error("--> creating user");
-        c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
+        c.preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(), "test_role").get();
         logger.error("--> waiting for .security index");
         ensureGreen(SECURITY_INDEX_NAME);
         logger.info("--> retrieving user");
@@ -252,13 +252,13 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray()));
         SearchResponse searchResp = client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get();
 
-        assertEquals(searchResp.getHits().getTotalHits(), 1L);
+        assertEquals(1L, searchResp.getHits().getTotalHits());
     }
 
     public void testUpdatingUserAndAuthentication() throws Exception {
         SecurityClient c = securityClient();
         logger.error("--> creating user");
-        c.preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get();
+        c.preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(), SecuritySettingsSource.TEST_ROLE).get();
         logger.error("--> waiting for .security index");
         ensureGreen(SECURITY_INDEX_NAME);
         logger.info("--> retrieving user");
@@ -273,9 +273,9 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray()));
         SearchResponse searchResp = client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get();
 
-        assertEquals(searchResp.getHits().getTotalHits(), 1L);
+        assertEquals(1L, searchResp.getHits().getTotalHits());
 
-        c.preparePutUser("joe", "s3krit2".toCharArray(), SecuritySettingsSource.TEST_ROLE).get();
+        c.preparePutUser("joe", "s3krit2".toCharArray(), getFastStoredHashAlgoForTests(), SecuritySettingsSource.TEST_ROLE).get();
 
         try {
             client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get();
@@ -287,13 +287,14 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
 
         token = basicAuthHeaderValue("joe", new SecureString("s3krit2".toCharArray()));
         searchResp = client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get();
-        assertEquals(searchResp.getHits().getTotalHits(), 1L);
+        assertEquals(1L, searchResp.getHits().getTotalHits());
     }
 
     public void testCreateDeleteAuthenticate() {
         SecurityClient c = securityClient();
         logger.error("--> creating user");
-        c.preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get();
+        c.preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(),
+            SecuritySettingsSource.TEST_ROLE).get();
         logger.error("--> waiting for .security index");
         ensureGreen(SECURITY_INDEX_NAME);
         logger.info("--> retrieving user");
@@ -308,7 +309,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray()));
         SearchResponse searchResp = client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get();
 
-        assertEquals(searchResp.getHits().getTotalHits(), 1L);
+        assertEquals(1L, searchResp.getHits().getTotalHits());
 
         DeleteUserResponse response = c.prepareDeleteUser("joe").get();
         assertThat(response.found(), is(true));
@@ -331,7 +332,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
                         new BytesArray("{\"match_all\": {}}"))
                 .get();
         logger.error("--> creating user");
-        c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
+        c.preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(), "test_role").get();
         logger.error("--> waiting for .security index");
         ensureGreen(SECURITY_INDEX_NAME);
 
@@ -380,7 +381,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
                 .addIndices(new String[]{"*"}, new String[]{"read"}, new String[]{"body", "title"}, null,
                         new BytesArray("{\"match_all\": {}}"))
                 .get();
-        c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
+        c.preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(), "test_role").get();
         logger.error("--> waiting for .security index");
         ensureGreen(SECURITY_INDEX_NAME);
 
@@ -414,7 +415,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         assertThat(client.prepareGetUsers("joes").get().hasUsers(), is(false));
         // check that putting a user without a password fails if the user doesn't exist
         try {
-            client.preparePutUser("joe", null, "admin_role").get();
+            client.preparePutUser("joe", null, getFastStoredHashAlgoForTests(), "admin_role").get();
             fail("cannot create a user without a password");
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("password must be specified"));
@@ -423,7 +424,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         assertThat(client.prepareGetUsers("joes").get().hasUsers(), is(false));
 
         // create joe with a password and verify the user works
-        client.preparePutUser("joe", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(), "admin_role").get();
+        client.preparePutUser("joe", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(),
+            getFastStoredHashAlgoForTests(), "admin_role").get();
         assertThat(client.prepareGetUsers("joe").get().hasUsers(), is(true));
         final String token = basicAuthHeaderValue("joe", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING);
         ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster()
@@ -431,7 +433,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         assertFalse(response.isTimedOut());
 
         // modify joe without sending the password
-        client.preparePutUser("joe", null, "read_role").fullName("Joe Smith").get();
+        client.preparePutUser("joe", null, getFastStoredHashAlgoForTests(), "read_role").fullName("Joe Smith").get();
         GetUsersResponse getUsersResponse = client.prepareGetUsers("joe").get();
         assertThat(getUsersResponse.hasUsers(), is(true));
         assertThat(getUsersResponse.users().length, is(1));
@@ -452,7 +454,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
 
         // update the user with password and admin role again
         String secondPassword = SecuritySettingsSourceField.TEST_PASSWORD + "2";
-        client.preparePutUser("joe", secondPassword.toCharArray(), "admin_role").fullName("Joe Smith").get();
+        client.preparePutUser("joe", secondPassword.toCharArray(), getFastStoredHashAlgoForTests(), "admin_role").
+            fullName("Joe Smith").get();
         getUsersResponse = client.prepareGetUsers("joe").get();
         assertThat(getUsersResponse.hasUsers(), is(true));
         assertThat(getUsersResponse.users().length, is(1));
@@ -480,7 +483,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
     public void testCannotCreateUserWithShortPassword() throws Exception {
         SecurityClient client = securityClient();
         try {
-            client.preparePutUser("joe", randomAlphaOfLengthBetween(0, 5).toCharArray(), "admin_role").get();
+            client.preparePutUser("joe", randomAlphaOfLengthBetween(0, 5).toCharArray(), getFastStoredHashAlgoForTests(),
+                "admin_role").get();
             fail("cannot create a user without a password < 6 characters");
         } catch (ValidationException v) {
             assertThat(v.getMessage().contains("password"), is(true));
@@ -490,7 +494,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
     public void testCannotCreateUserWithInvalidCharactersInName() throws Exception {
         SecurityClient client = securityClient();
         ValidationException v = expectThrows(ValidationException.class,
-                () -> client.preparePutUser("fóóbár", "my-am@zing-password".toCharArray(), "admin_role").get()
+            () -> client.preparePutUser("fóóbár", "my-am@zing-password".toCharArray(), getFastStoredHashAlgoForTests(),
+                "admin_role").get()
         );
         assertThat(v.getMessage(), containsString("names must be"));
     }
@@ -500,7 +505,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
 
         SecurityClient client = securityClient();
         if (randomBoolean()) {
-            client.preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get();
+            client.preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(),
+                SecuritySettingsSource.TEST_ROLE).get();
         } else {
             client.preparePutRole("read_role")
                     .cluster("none")
@@ -520,7 +526,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         final String username = randomFrom(ElasticUser.NAME, KibanaUser.NAME);
         IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
                 () -> securityClient().preparePutUser(username, randomBoolean() ? SecuritySettingsSourceField.TEST_PASSWORD.toCharArray()
-                        : null, "admin").get());
+                    : null, getFastStoredHashAlgoForTests(), "admin").get());
         assertThat(exception.getMessage(), containsString("Username [" + username + "] is reserved"));
 
         exception = expectThrows(IllegalArgumentException.class,
@@ -532,19 +538,22 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         assertThat(exception.getMessage(), containsString("user [" + AnonymousUser.DEFAULT_ANONYMOUS_USERNAME + "] is anonymous"));
 
         exception = expectThrows(IllegalArgumentException.class,
-                () -> securityClient().prepareChangePassword(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME, "foobar".toCharArray()).get());
+            () -> securityClient().prepareChangePassword(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME, "foobar".toCharArray(),
+                getFastStoredHashAlgoForTests()).get());
         assertThat(exception.getMessage(), containsString("user [" + AnonymousUser.DEFAULT_ANONYMOUS_USERNAME + "] is anonymous"));
 
         exception = expectThrows(IllegalArgumentException.class,
-                () -> securityClient().preparePutUser(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME, "foobar".toCharArray()).get());
+            () -> securityClient().preparePutUser(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME, "foobar".toCharArray(),
+                getFastStoredHashAlgoForTests()).get());
         assertThat(exception.getMessage(), containsString("Username [" + AnonymousUser.DEFAULT_ANONYMOUS_USERNAME + "] is reserved"));
 
         exception = expectThrows(IllegalArgumentException.class,
-                () -> securityClient().preparePutUser(SystemUser.NAME, "foobar".toCharArray()).get());
+            () -> securityClient().preparePutUser(SystemUser.NAME, "foobar".toCharArray(), getFastStoredHashAlgoForTests()).get());
         assertThat(exception.getMessage(), containsString("user [" + SystemUser.NAME + "] is internal"));
 
         exception = expectThrows(IllegalArgumentException.class,
-                () -> securityClient().prepareChangePassword(SystemUser.NAME, "foobar".toCharArray()).get());
+            () -> securityClient().prepareChangePassword(SystemUser.NAME, "foobar".toCharArray(),
+                getFastStoredHashAlgoForTests()).get());
         assertThat(exception.getMessage(), containsString("user [" + SystemUser.NAME + "] is internal"));
 
         exception = expectThrows(IllegalArgumentException.class,
@@ -582,7 +591,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
     }
 
     public void testCreateAndChangePassword() throws Exception {
-        securityClient().preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get();
+        securityClient().preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(),
+            SecuritySettingsSource.TEST_ROLE).get();
         final String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray()));
         ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token))
                 .admin().cluster().prepareHealth().get();
@@ -590,8 +600,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
 
         ChangePasswordResponse passwordResponse = securityClient(
                 client().filterWithHeader(Collections.singletonMap("Authorization", token)))
-                .prepareChangePassword("joe", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray())
-                .get();
+            .prepareChangePassword("joe", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(), getFastStoredHashAlgoForTests()).get();
         assertThat(passwordResponse, notNullValue());
 
 
@@ -671,7 +680,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
         final int numNativeUsers = scaledRandomIntBetween(1, 32);
         SecurityClient securityClient = new SecurityClient(client());
         for (int i = 0; i < numNativeUsers; i++) {
-            securityClient.preparePutUser("joe" + i, "s3krit".toCharArray(), "superuser").get();
+            securityClient.preparePutUser("joe" + i, "s3krit".toCharArray(), getFastStoredHashAlgoForTests(),
+                "superuser").get();
         }
 
         XPackUsageResponse response = new XPackUsageRequestBuilder(client()).get();
@@ -690,7 +700,9 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
     }
 
     public void testSetEnabled() throws Exception {
-        securityClient().preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get();
+
+        securityClient().preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(),
+            SecuritySettingsSource.TEST_ROLE).get();
         final String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray()));
         ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token))
                 .admin().cluster().prepareHealth().get();
@@ -714,7 +726,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
 
     public void testNegativeLookupsThenCreateRole() throws Exception {
         SecurityClient securityClient = new SecurityClient(client());
-        securityClient.preparePutUser("joe", "s3krit".toCharArray(), "unknown_role").get();
+        securityClient.preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(), "unknown_role").get();
 
         final int negativeLookups = scaledRandomIntBetween(1, 10);
         for (int i = 0; i < negativeLookups; i++) {
@@ -750,8 +762,9 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
      * the loader returned a null value, while the other caller(s) would get a null value unexpectedly
      */
     public void testConcurrentRunAs() throws Exception {
-        securityClient().preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get();
-        securityClient().preparePutUser("executor", "s3krit".toCharArray(), "superuser").get();
+        securityClient().preparePutUser("joe", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(), SecuritySettingsSource
+            .TEST_ROLE).get();
+        securityClient().preparePutUser("executor", "s3krit".toCharArray(), getFastStoredHashAlgoForTests(), "superuser").get();
         final String token = basicAuthHeaderValue("executor", new SecureString("s3krit".toCharArray()));
         final Client client = client().filterWithHeader(MapBuilder.<String, String>newMapBuilder()
                 .put("Authorization", token)

+ 3 - 2
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java

@@ -121,7 +121,7 @@ public class NativeUsersStoreTests extends ESTestCase {
         final NativeUsersStore.ReservedUserInfo userInfo = future.get();
         assertThat(userInfo.hasEmptyPassword, equalTo(true));
         assertThat(userInfo.enabled, equalTo(true));
-        assertThat(userInfo.passwordHash, equalTo(ReservedRealm.EMPTY_PASSWORD_HASH));
+        assertTrue(Hasher.verifyHash(new SecureString("".toCharArray()), userInfo.passwordHash));
     }
 
     public void testVerifyUserWithCorrectPassword() throws Exception {
@@ -207,6 +207,7 @@ public class NativeUsersStoreTests extends ESTestCase {
     }
 
     private void respondToGetUserRequest(String username, SecureString password, String[] roles) throws IOException {
+        // Native users store is initiated with default hashing algorithm
         final Map<String, Object> values = new HashMap<>();
         values.put(User.Fields.USERNAME.getPreferredName(), username);
         values.put(User.Fields.PASSWORD.getPreferredName(), String.valueOf(Hasher.BCRYPT.hash(password)));
@@ -241,4 +242,4 @@ public class NativeUsersStoreTests extends ESTestCase {
         return new NativeUsersStore(Settings.EMPTY, client, securityIndex);
     }
 
-}
+}

+ 1 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmIntegTests.java

@@ -76,7 +76,7 @@ public class ReservedRealmIntegTests extends NativeRealmIntegTestCase {
         }
 
         ChangePasswordResponse response = securityClient()
-                .prepareChangePassword(username, Arrays.copyOf(newPassword, newPassword.length))
+            .prepareChangePassword(username, Arrays.copyOf(newPassword, newPassword.length), getFastStoredHashAlgoForTests())
                 .get();
         assertThat(response, notNullValue());
 

+ 47 - 32
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java

@@ -77,12 +77,21 @@ public class ReservedRealmTests extends ESTestCase {
         when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
     }
 
+    public void testInvalidHashingAlgorithmFails() {
+        final String invalidAlgoId = randomFrom("sha1", "md5", "noop");
+        final Settings invalidSettings = Settings.builder().put("xpack.security.authc.password_hashing.algorithm", invalidAlgoId).build();
+        IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> new ReservedRealm(mock(Environment.class),
+            invalidSettings, usersStore, new AnonymousUser(Settings.EMPTY), securityIndex, threadPool));
+        assertThat(exception.getMessage(), containsString(invalidAlgoId));
+        assertThat(exception.getMessage(), containsString("Only pbkdf2 or bcrypt family algorithms can be used for password hashing"));
+    }
+
     public void testReservedUserEmptyPasswordAuthenticationFails() throws Throwable {
         final String principal = randomFrom(UsernamesField.ELASTIC_NAME, UsernamesField.KIBANA_NAME, UsernamesField.LOGSTASH_NAME,
-                UsernamesField.BEATS_NAME);
+            UsernamesField.BEATS_NAME);
 
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore,
-                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
 
         PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
 
@@ -97,8 +106,8 @@ public class ReservedRealmTests extends ESTestCase {
             when(securityIndex.indexExists()).thenReturn(true);
         }
         final ReservedRealm reservedRealm =
-                new ReservedRealm(mock(Environment.class), settings, usersStore,
-                        new AnonymousUser(settings), securityIndex, threadPool);
+            new ReservedRealm(mock(Environment.class), settings, usersStore,
+                new AnonymousUser(settings), securityIndex, threadPool);
         final User expected = randomReservedUser(true);
         final String principal = expected.principal();
 
@@ -120,14 +129,16 @@ public class ReservedRealmTests extends ESTestCase {
 
     private void verifySuccessfulAuthentication(boolean enabled) throws Exception {
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore,
-                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
         final User expectedUser = randomReservedUser(enabled);
         final String principal = expectedUser.principal();
         final SecureString newPassword = new SecureString("foobar".toCharArray());
+        // Mocked users store is initiated with default hashing algorithm
+        final Hasher hasher = Hasher.resolve("bcrypt");
         when(securityIndex.indexExists()).thenReturn(true);
         doAnswer((i) -> {
             ActionListener callback = (ActionListener) i.getArguments()[1];
-            callback.onResponse(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), enabled, false));
+            callback.onResponse(new ReservedUserInfo(hasher.hash(newPassword), enabled, false));
             return null;
         }).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
 
@@ -139,7 +150,7 @@ public class ReservedRealmTests extends ESTestCase {
         // the realm assumes it owns the hashed password so it fills it with 0's
         doAnswer((i) -> {
             ActionListener callback = (ActionListener) i.getArguments()[1];
-            callback.onResponse(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), true, false));
+            callback.onResponse(new ReservedUserInfo(hasher.hash(newPassword), true, false));
             return null;
         }).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
 
@@ -160,8 +171,8 @@ public class ReservedRealmTests extends ESTestCase {
 
     public void testLookup() throws Exception {
         final ReservedRealm reservedRealm =
-                new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore,
-                        new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore,
+                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
         final User expectedUser = randomReservedUser(true);
         final String principal = expectedUser.principal();
 
@@ -185,8 +196,8 @@ public class ReservedRealmTests extends ESTestCase {
     public void testLookupDisabled() throws Exception {
         Settings settings = Settings.builder().put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false).build();
         final ReservedRealm reservedRealm =
-                new ReservedRealm(mock(Environment.class), settings, usersStore, new AnonymousUser(settings),
-                        securityIndex, threadPool);
+            new ReservedRealm(mock(Environment.class), settings, usersStore, new AnonymousUser(settings),
+                securityIndex, threadPool);
         final User expectedUser = randomReservedUser(true);
         final String principal = expectedUser.principal();
 
@@ -199,8 +210,8 @@ public class ReservedRealmTests extends ESTestCase {
 
     public void testLookupThrows() throws Exception {
         final ReservedRealm reservedRealm =
-                new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore,
-                        new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore,
+                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
         final User expectedUser = randomReservedUser(true);
         final String principal = expectedUser.principal();
         when(securityIndex.indexExists()).thenReturn(true);
@@ -247,22 +258,22 @@ public class ReservedRealmTests extends ESTestCase {
 
     public void testGetUsers() {
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore,
-                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
         PlainActionFuture<Collection<User>> userFuture = new PlainActionFuture<>();
         reservedRealm.users(userFuture);
         assertThat(userFuture.actionGet(),
-                containsInAnyOrder(new ElasticUser(true), new KibanaUser(true), new LogstashSystemUser(true), new BeatsSystemUser(true)));
+            containsInAnyOrder(new ElasticUser(true), new KibanaUser(true), new LogstashSystemUser(true), new BeatsSystemUser(true)));
     }
 
     public void testGetUsersDisabled() {
         final boolean anonymousEnabled = randomBoolean();
         Settings settings = Settings.builder()
-                .put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false)
-                .put(AnonymousUser.ROLES_SETTING.getKey(), anonymousEnabled ? "user" : "")
-                .build();
+            .put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false)
+            .put(AnonymousUser.ROLES_SETTING.getKey(), anonymousEnabled ? "user" : "")
+            .build();
         final AnonymousUser anonymousUser = new AnonymousUser(settings);
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, anonymousUser,
-                securityIndex, threadPool);
+            securityIndex, threadPool);
         PlainActionFuture<Collection<User>> userFuture = new PlainActionFuture<>();
         reservedRealm.users(userFuture);
         if (anonymousEnabled) {
@@ -275,11 +286,13 @@ public class ReservedRealmTests extends ESTestCase {
     public void testFailedAuthentication() throws Exception {
         when(securityIndex.indexExists()).thenReturn(true);
         SecureString password = new SecureString("password".toCharArray());
-        char[] hash = Hasher.BCRYPT.hash(password);
+        // Mocked users store is initiated with default hashing algorithm
+        final Hasher hasher = Hasher.resolve("bcrypt");
+        char[] hash = hasher.hash(password);
         ReservedUserInfo userInfo = new ReservedUserInfo(hash, true, false);
         mockGetAllReservedUserInfo(usersStore, Collections.singletonMap("elastic", userInfo));
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore,
-                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
 
         if (randomBoolean()) {
             PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
@@ -309,7 +322,7 @@ public class ReservedRealmTests extends ESTestCase {
         when(securityIndex.indexExists()).thenReturn(true);
 
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
-                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
         PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
 
         doAnswer((i) -> {
@@ -318,8 +331,8 @@ public class ReservedRealmTests extends ESTestCase {
             return null;
         }).when(usersStore).getReservedUserInfo(eq("elastic"), any(ActionListener.class));
         reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(),
-                        mockSecureSettings.getString("bootstrap.password")),
-                listener);
+                mockSecureSettings.getString("bootstrap.password")),
+            listener);
         final AuthenticationResult result = listener.get();
         assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
     }
@@ -331,18 +344,20 @@ public class ReservedRealmTests extends ESTestCase {
         when(securityIndex.indexExists()).thenReturn(true);
 
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
-                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
         PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
         SecureString password = new SecureString("password".toCharArray());
+        // Mocked users store is initiated with default hashing algorithm
+        final Hasher hasher = Hasher.resolve("bcrypt");
         doAnswer((i) -> {
             ActionListener callback = (ActionListener) i.getArguments()[1];
-            char[] hash = Hasher.BCRYPT.hash(password);
+            char[] hash = hasher.hash(password);
             ReservedUserInfo userInfo = new ReservedUserInfo(hash, true, false);
             callback.onResponse(userInfo);
             return null;
         }).when(usersStore).getReservedUserInfo(eq("elastic"), any(ActionListener.class));
         reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(),
-                mockSecureSettings.getString("bootstrap.password")), listener);
+            mockSecureSettings.getString("bootstrap.password")), listener);
         assertFailedAuthentication(listener, "elastic");
         // now try with the real password
         listener = new PlainActionFuture<>();
@@ -358,12 +373,12 @@ public class ReservedRealmTests extends ESTestCase {
         when(securityIndex.indexExists()).thenReturn(false);
 
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
-                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
         PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
 
         reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(),
-                        mockSecureSettings.getString("bootstrap.password")),
-                listener);
+                mockSecureSettings.getString("bootstrap.password")),
+            listener);
         final AuthenticationResult result = listener.get();
         assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
     }
@@ -376,7 +391,7 @@ public class ReservedRealmTests extends ESTestCase {
         when(securityIndex.indexExists()).thenReturn(true);
 
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
-                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
         PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
 
         final String principal = randomFrom(KibanaUser.NAME, LogstashSystemUser.NAME, BeatsSystemUser.NAME);
@@ -398,7 +413,7 @@ public class ReservedRealmTests extends ESTestCase {
         when(securityIndex.indexExists()).thenReturn(false);
 
         final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
-                new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
+            new AnonymousUser(Settings.EMPTY), securityIndex, threadPool);
         PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
 
         final String principal = randomFrom(KibanaUser.NAME, LogstashSystemUser.NAME, BeatsSystemUser.NAME);

+ 4 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java

@@ -58,7 +58,8 @@ public class FileRealmTests extends ESTestCase {
     public void init() throws Exception {
         userPasswdStore = mock(FileUserPasswdStore.class);
         userRolesStore = mock(FileUserRolesStore.class);
-        globalSettings = Settings.builder().put("path.home", createTempDir()).build();
+        globalSettings = Settings.builder().put("path.home", createTempDir()).put("xpack.security.authc.password_hashing.algorithm",
+            randomFrom("bcrypt9", "pbkdf2")).build();
         threadPool = mock(ThreadPool.class);
         threadContext = new ThreadContext(globalSettings);
         when(threadPool.getThreadContext()).thenReturn(threadContext);
@@ -85,10 +86,10 @@ public class FileRealmTests extends ESTestCase {
 
     public void testAuthenticateCaching() throws Exception {
         Settings settings = Settings.builder()
-                .put("cache.hash_algo", Hasher.values()[randomIntBetween(0, Hasher.values().length - 1)].name().toLowerCase(Locale.ROOT))
-                .build();
+            .put("cache.hash_algo", Hasher.values()[randomIntBetween(0, Hasher.values().length - 1)].name().toLowerCase(Locale.ROOT)).build();
         RealmConfig config = new RealmConfig("file-test", settings, globalSettings, TestEnvironment.newEnvironment(globalSettings),
             threadContext);
+
         when(userPasswdStore.verifyPassword(eq("user1"), eq(new SecureString("test123")), any(Supplier.class)))
                 .thenAnswer(VERIFY_PASSWORD_ANSWER);
         when(userRolesStore.roles("user1")).thenReturn(new String[]{"role1", "role2"});

+ 27 - 14
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java

@@ -53,9 +53,11 @@ public class FileUserPasswdStoreTests extends ESTestCase {
     @Before
     public void init() {
         settings = Settings.builder()
-                .put("resource.reload.interval.high", "2s")
-                .put("path.home", createTempDir())
-                .build();
+            .put("resource.reload.interval.high", "2s")
+            .put("path.home", createTempDir())
+            .put("xpack.security.authc.password_hashing.algorithm", randomFrom("bcrypt", "bcrypt11", "pbkdf2", "pbkdf2_1000",
+                "pbkdf2_50000"))
+            .build();
         env = TestEnvironment.newEnvironment(settings);
         threadPool = new TestThreadPool("test");
     }
@@ -86,17 +88,18 @@ public class FileUserPasswdStoreTests extends ESTestCase {
         Files.createDirectories(xpackConf);
         Path file = xpackConf.resolve("users");
         Files.copy(users, file, StandardCopyOption.REPLACE_EXISTING);
-
+        final Hasher hasher = Hasher.resolve(settings.get("xpack.security.authc.password_hashing.algorithm"));
         Settings fileSettings = randomBoolean() ? Settings.EMPTY : Settings.builder().put("files.users", file.toAbsolutePath()).build();
         RealmConfig config = new RealmConfig("file-test", fileSettings, settings, env, threadPool.getThreadContext());
         ResourceWatcherService watcherService = new ResourceWatcherService(settings, threadPool);
         final CountDownLatch latch = new CountDownLatch(1);
 
         FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService, latch::countDown);
-
-        User user = new User("bcrypt");
-        assertThat(store.userExists("bcrypt"), is(true));
-        AuthenticationResult result = store.verifyPassword("bcrypt", new SecureString("test123"), () -> user);
+        //Test users share the hashing algorithm name for convenience
+        String username = settings.get("xpack.security.authc.password_hashing.algorithm");
+        User user = new User(username);
+        assertThat(store.userExists(username), is(true));
+        AuthenticationResult result = store.verifyPassword(username, new SecureString("test123"), () -> user);
         assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
         assertThat(result.getUser(), is(user));
 
@@ -104,7 +107,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
 
         try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8, StandardOpenOption.APPEND)) {
             writer.newLine();
-            writer.append("foobar:").append(new String(Hasher.BCRYPT.hash(new SecureString("barfoo"))));
+            writer.append("foobar:").append(new String(hasher.hash(new SecureString("barfoo"))));
         }
 
         if (!latch.await(5, TimeUnit.SECONDS)) {
@@ -133,9 +136,10 @@ public class FileUserPasswdStoreTests extends ESTestCase {
         final CountDownLatch latch = new CountDownLatch(1);
 
         FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService, latch::countDown);
-
-        User user = new User("bcrypt");
-        final AuthenticationResult result = store.verifyPassword("bcrypt", new SecureString("test123"), () -> user);
+        //Test users share the hashing algorithm name for convenience
+        String username = settings.get("xpack.security.authc.password_hashing.algorithm");
+        User user = new User(username);
+        final AuthenticationResult result = store.verifyPassword(username, new SecureString("test123"), () -> user);
         assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
         assertThat(result.getUser(), is(user));
 
@@ -155,11 +159,11 @@ public class FileUserPasswdStoreTests extends ESTestCase {
         Path path = getDataPath("users");
         Map<String, char[]> users = FileUserPasswdStore.parseFile(path, null, Settings.EMPTY);
         assertThat(users, notNullValue());
-        assertThat(users.size(), is(6));
+        assertThat(users.size(), is(11));
         assertThat(users.get("bcrypt"), notNullValue());
         assertThat(new String(users.get("bcrypt")), equalTo("$2a$05$zxnP0vdREMxnEpkLCDI2OuSaSk/QEKA2.A42iOpI6U2u.RLLOWm1e"));
         assertThat(users.get("bcrypt10"), notNullValue());
-        assertThat(new String(users.get("bcrypt10")), equalTo("$2y$10$FMhmFjwU5.qxQ/BsEciS9OqcJVkFMgXMo4uH5CelOR1j4N9zIv67e"));
+        assertThat(new String(users.get("bcrypt10")), equalTo("$2a$10$cFxpMx6YDrH/PXwLpTlux.KVykN1TG2Pgdl5oJX5/G/KYp3G6jbFG"));
         assertThat(users.get("md5"), notNullValue());
         assertThat(new String(users.get("md5")), equalTo("$apr1$R3DdqiAZ$aljIkaIVPSarmDMlJUBBP."));
         assertThat(users.get("crypt"), notNullValue());
@@ -168,6 +172,15 @@ public class FileUserPasswdStoreTests extends ESTestCase {
         assertThat(new String(users.get("plain")), equalTo("{plain}test123"));
         assertThat(users.get("sha"), notNullValue());
         assertThat(new String(users.get("sha")), equalTo("{SHA}cojt0Pw//L6ToM8G41aOKFIWh7w="));
+        assertThat(users.get("pbkdf2"), notNullValue());
+        assertThat(new String(users.get("pbkdf2")),
+            equalTo("{PBKDF2}10000$ekcItXk4jtK2bBjbVk0rZuWRjT0DoQqQJOIfyMeLIxg=$RA2/Nn1jRi8QskRS5IVotCV0FBO6M8DlNXC37GKa/8c="));
+        assertThat(users.get("pbkdf2_1000"), notNullValue());
+        assertThat(new String(users.get("pbkdf2_1000")),
+            equalTo("{PBKDF2}1000$32yPZSShxuKYAl47ip0g6VwbFrD8tvFJuQCoRPGhXC8=$cXAE1BkBXRmkv7pQA7fw4TZ1+rFWS2/nZGeA3kL1Eu8="));
+        assertThat(users.get("pbkdf2_50000"), notNullValue());
+        assertThat(new String(users.get("pbkdf2_50000")),
+            equalTo("{PBKDF2}50000$z1CLJt0MEFjkIK5iEfgvfnA6xq7lF25uasspsTKSo5Q=$XxCVLbaKDimOdyWgLCLJiyoiWpA/XDMe/xtVgn1r5Sg="));
     }
 
     public void testParseFile_Empty() throws Exception {

+ 10 - 12
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java

@@ -19,7 +19,6 @@ import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
 import org.elasticsearch.xpack.core.security.authc.Realm;
 import org.elasticsearch.xpack.core.security.authc.RealmConfig;
-import org.elasticsearch.xpack.core.security.authc.support.BCrypt;
 import org.elasticsearch.xpack.core.security.authc.support.CachingUsernamePasswordRealmSettings;
 import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
@@ -29,6 +28,7 @@ import org.junit.Before;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -61,12 +61,11 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
     }
 
     public void testSettings() throws Exception {
-        String hashAlgo = randomFrom("bcrypt", "bcrypt4", "bcrypt5", "bcrypt6", "bcrypt7", "bcrypt8", "bcrypt9",
-                "sha1", "ssha256", "md5", "clear_text", "noop");
+        String cachingHashAlgo = Hasher.values()[randomIntBetween(0, Hasher.values().length - 1)].name().toLowerCase(Locale.ROOT);
         int maxUsers = randomIntBetween(10, 100);
         TimeValue ttl = TimeValue.timeValueMinutes(randomIntBetween(10, 20));
         Settings settings = Settings.builder()
-                .put(CachingUsernamePasswordRealmSettings.CACHE_HASH_ALGO_SETTING.getKey(), hashAlgo)
+                .put(CachingUsernamePasswordRealmSettings.CACHE_HASH_ALGO_SETTING.getKey(), cachingHashAlgo)
                 .put(CachingUsernamePasswordRealmSettings.CACHE_MAX_USERS_SETTING.getKey(), maxUsers)
                 .put(CachingUsernamePasswordRealmSettings.CACHE_TTL_SETTING.getKey(), ttl)
                 .build();
@@ -84,8 +83,7 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
                 listener.onFailure(new UnsupportedOperationException("this method should not be called"));
             }
         };
-
-        assertThat(realm.hasher, sameInstance(Hasher.resolve(hashAlgo)));
+        assertThat(realm.cacheHasher, sameInstance(Hasher.resolve(cachingHashAlgo)));
     }
 
     public void testAuthCache() {
@@ -347,8 +345,8 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
         final String username = "username";
         final SecureString password = SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING;
         final AtomicInteger authCounter = new AtomicInteger(0);
-
-        final String passwordHash = new String(Hasher.BCRYPT.hash(password));
+        final Hasher pwdHasher = Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9"));
+        final String passwordHash = new String(pwdHasher.hash(password));
         RealmConfig config = new RealmConfig("test_realm", Settings.EMPTY, globalSettings, TestEnvironment.newEnvironment(globalSettings),
             new ThreadContext(Settings.EMPTY));
         final CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config, threadPool) {
@@ -356,7 +354,7 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
             protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener) {
                 authCounter.incrementAndGet();
                 // do something slow
-                if (BCrypt.checkpw(token.credentials(), passwordHash)) {
+                if (pwdHasher.verify(token.credentials(), passwordHash.toCharArray())) {
                     listener.onResponse(AuthenticationResult.success(new User(username, new String[]{"r1", "r2", "r3"})));
                 } else {
                     listener.onFailure(new IllegalStateException("password auth should never fail"));
@@ -413,15 +411,15 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
         final String username = "username";
         final SecureString password = SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING;
         final SecureString randomPassword = new SecureString(randomAlphaOfLength(password.length()).toCharArray());
-
-        final String passwordHash = new String(Hasher.BCRYPT.hash(password));
+        final Hasher localHasher = Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9"));
+        final String passwordHash = new String(localHasher.hash(password));
         RealmConfig config = new RealmConfig("test_realm", Settings.EMPTY, globalSettings, TestEnvironment.newEnvironment(globalSettings),
                 new ThreadContext(Settings.EMPTY));
         final CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config, threadPool) {
             @Override
             protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener) {
                 // do something slow
-                if (BCrypt.checkpw(token.credentials(), passwordHash)) {
+                if (localHasher.verify(token.credentials(), passwordHash.toCharArray())) {
                     listener.onResponse(AuthenticationResult.success(new User(username, new String[]{"r1", "r2", "r3"})));
                 } else {
                     listener.onResponse(AuthenticationResult.unsuccessful("Incorrect password", null));

+ 83 - 10
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/HasherTests.java

@@ -9,6 +9,7 @@ import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 
+import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.sameInstance;
 
 public class HasherTests extends ESTestCase {
@@ -20,6 +21,21 @@ public class HasherTests extends ESTestCase {
         testHasherSelfGenerated(Hasher.BCRYPT7);
         testHasherSelfGenerated(Hasher.BCRYPT8);
         testHasherSelfGenerated(Hasher.BCRYPT9);
+        testHasherSelfGenerated(Hasher.BCRYPT10);
+        testHasherSelfGenerated(Hasher.BCRYPT11);
+        testHasherSelfGenerated(Hasher.BCRYPT12);
+        testHasherSelfGenerated(Hasher.BCRYPT13);
+        testHasherSelfGenerated(Hasher.BCRYPT14);
+    }
+
+    public void testPBKDF2FamilySelfGenerated() throws Exception {
+        testHasherSelfGenerated(Hasher.PBKDF2);
+        testHasherSelfGenerated(Hasher.PBKDF2_1000);
+        testHasherSelfGenerated(Hasher.PBKDF2_10000);
+        testHasherSelfGenerated(Hasher.PBKDF2_50000);
+        testHasherSelfGenerated(Hasher.PBKDF2_100000);
+        testHasherSelfGenerated(Hasher.PBKDF2_500000);
+        testHasherSelfGenerated(Hasher.PBKDF2_1000000);
     }
 
     public void testMd5SelfGenerated() throws Exception {
@@ -38,7 +54,7 @@ public class HasherTests extends ESTestCase {
         testHasherSelfGenerated(Hasher.NOOP);
     }
 
-    public void testResolve() throws Exception {
+    public void testResolve() {
         assertThat(Hasher.resolve("bcrypt"), sameInstance(Hasher.BCRYPT));
         assertThat(Hasher.resolve("bcrypt4"), sameInstance(Hasher.BCRYPT4));
         assertThat(Hasher.resolve("bcrypt5"), sameInstance(Hasher.BCRYPT5));
@@ -46,23 +62,80 @@ public class HasherTests extends ESTestCase {
         assertThat(Hasher.resolve("bcrypt7"), sameInstance(Hasher.BCRYPT7));
         assertThat(Hasher.resolve("bcrypt8"), sameInstance(Hasher.BCRYPT8));
         assertThat(Hasher.resolve("bcrypt9"), sameInstance(Hasher.BCRYPT9));
+        assertThat(Hasher.resolve("bcrypt10"), sameInstance(Hasher.BCRYPT));
+        assertThat(Hasher.resolve("bcrypt11"), sameInstance(Hasher.BCRYPT11));
+        assertThat(Hasher.resolve("bcrypt12"), sameInstance(Hasher.BCRYPT12));
+        assertThat(Hasher.resolve("bcrypt13"), sameInstance(Hasher.BCRYPT13));
+        assertThat(Hasher.resolve("bcrypt14"), sameInstance(Hasher.BCRYPT14));
+        assertThat(Hasher.resolve("pbkdf2"), sameInstance(Hasher.PBKDF2));
+        assertThat(Hasher.resolve("pbkdf2_1000"), sameInstance(Hasher.PBKDF2_1000));
+        assertThat(Hasher.resolve("pbkdf2_10000"), sameInstance(Hasher.PBKDF2));
+        assertThat(Hasher.resolve("pbkdf2_50000"), sameInstance(Hasher.PBKDF2_50000));
+        assertThat(Hasher.resolve("pbkdf2_100000"), sameInstance(Hasher.PBKDF2_100000));
+        assertThat(Hasher.resolve("pbkdf2_500000"), sameInstance(Hasher.PBKDF2_500000));
+        assertThat(Hasher.resolve("pbkdf2_1000000"), sameInstance(Hasher.PBKDF2_1000000));
         assertThat(Hasher.resolve("sha1"), sameInstance(Hasher.SHA1));
         assertThat(Hasher.resolve("md5"), sameInstance(Hasher.MD5));
         assertThat(Hasher.resolve("ssha256"), sameInstance(Hasher.SSHA256));
         assertThat(Hasher.resolve("noop"), sameInstance(Hasher.NOOP));
         assertThat(Hasher.resolve("clear_text"), sameInstance(Hasher.NOOP));
-        try {
+        IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
             Hasher.resolve("unknown_hasher");
-            fail("expected a settings error when trying to resolve an unknown hasher");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-        Hasher hasher = randomFrom(Hasher.values());
-        assertThat(Hasher.resolve("unknown_hasher", hasher), sameInstance(hasher));
+        });
+        assertThat(e.getMessage(), containsString("unknown hash function "));
+    }
+
+    public void testResolveFromHash() {
+        assertThat(Hasher.resolveFromHash("$2a$10$1oZj.8KmlwiCy4DWKvDH3OU0Ko4WRF4FknyvCh3j/ZtaRCNYA6Xzm".toCharArray()),
+            sameInstance(Hasher.BCRYPT));
+        assertThat(Hasher.resolveFromHash("$2a$04$GwJtIQiGMHASEYphMiCpjeZh1cDyYC5U.DKfNKa4i/y0IbOvc2LiG".toCharArray()),
+            sameInstance(Hasher.BCRYPT4));
+        assertThat(Hasher.resolveFromHash("$2a$05$xLmwSB7Nw7PcqP.6hXdc4eUZbT.4.iAZ3CTPzSaUibrrYjC6Vwq1m".toCharArray()),
+            sameInstance(Hasher.BCRYPT5));
+        assertThat(Hasher.resolveFromHash("$2a$06$WQX1MALAjVOhR2YKmLcHYed2oROzBl3OZPtvq3FkVZYwm9X2LVKYm".toCharArray()),
+            sameInstance(Hasher.BCRYPT6));
+        assertThat(Hasher.resolveFromHash("$2a$07$Satxnu2fCvwYXpHIk8A2sO2uwROrsV7WrNiRJPq1oXEl5lc9FE.7S".toCharArray()),
+            sameInstance(Hasher.BCRYPT7));
+        assertThat(Hasher.resolveFromHash("$2a$08$LLfkTt2C9TUl5sDtgqmE3uRw9nHt748d3eMSGfbFYgQQQhjbXHFo2".toCharArray()),
+            sameInstance(Hasher.BCRYPT8));
+        assertThat(Hasher.resolveFromHash("$2a$09$.VCWA3yFVdd6gfI526TUrufb4TvxMuhW0jIuMfhd4/fy1Ak/zrSFe".toCharArray()),
+            sameInstance(Hasher.BCRYPT9));
+        assertThat(Hasher.resolveFromHash("$2a$10$OEiXFrUUY02Nm7YsEgzFuuJ3yO3HAYzJUU7omseluy28s7FYaictu".toCharArray()),
+            sameInstance(Hasher.BCRYPT));
+        assertThat(Hasher.resolveFromHash("$2a$11$Ya53LCozFlKABu05xsAbj.9xmrczyuAY/fTvxKkDiHOJc5GYcaNRy".toCharArray()),
+            sameInstance(Hasher.BCRYPT11));
+        assertThat(Hasher.resolveFromHash("$2a$12$oUW2hiWBHYwbJamWi6YDPeKS2NBCvD4GR50zh9QZCcgssNFcbpg/a".toCharArray()),
+            sameInstance(Hasher.BCRYPT12));
+        assertThat(Hasher.resolveFromHash("$2a$13$0PDx6mxKK4bLSgpc5H6eaeylWub7UFghjxV03lFYSz4WS4slDT30q".toCharArray()),
+            sameInstance(Hasher.BCRYPT13));
+        assertThat(Hasher.resolveFromHash("$2a$14$lFyXmX7p9/FHr7W4nxTnfuCkjAoBHv6awQlv8jlKZ/YCMI65i38e6".toCharArray()),
+            sameInstance(Hasher.BCRYPT14));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2}1000$oNl3JWiDZhXqhrpk9Kl+T0tKpVNNV3UHNxENPePpo2M=$g9lERDX5op20eX534bHdQy7ySRwobxwtaxxsz3AYPIU=".toCharArray()),
+            sameInstance(Hasher.PBKDF2_1000));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2}10000$UrwrHBY4GA1na9KxRpoFkUiICTeZe+mMZCZOg6bRSLc=$1Wl32wRQ9Q3Sv1IFoNwgSrUa5YifLv0MoxAO6leyip8=".toCharArray()),
+            sameInstance(Hasher.PBKDF2));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2}50000$mxa5m9AlgtKLUXKi/pE5+4w7ZexGSOtlUHD043NHVdc=$LE5Ncph672M8PtugfRgk2k3ue9qY2cKgiguuAd+e3I0=".toCharArray()),
+            sameInstance(Hasher.PBKDF2_50000));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2}100000$qFs8H0FjietnI7sgr/1Av4H+Z7d/9dehfZ2ptU474jk=$OFj40Ha0XcHWUXSspRx6EeXnTcuN0Nva2/i2c/hvnZE=".toCharArray()),
+            sameInstance(Hasher.PBKDF2_100000));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2}500000$wyttuDlppd5KYD35uDZN6vudB50Cjshm5efZhOxZZQI=$ADZpOHY6llJZsjupZCn6s4Eocg0dKKdBiNjDBYqhlzA=".toCharArray()),
+            sameInstance(Hasher.PBKDF2_500000));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2}1000000$UuyhtjDEzWmE2wyY80akZKPWWpy2r2X50so41YML82U=$WFasYLelqbjQwt3EqFlUcwHiC38EZC45Iu/Iz0xL1GQ=".toCharArray()),
+            sameInstance(Hasher.PBKDF2_1000000));
+        IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
+            Hasher.resolveFromHash("{GBGN}cGR8S2vr3FuFuOpQitR".toCharArray());
+        });
+        assertThat(e.getMessage(), containsString("unknown hash format for hash"));
     }
 
-    private static void testHasherSelfGenerated(Hasher hasher) throws Exception {
-        SecureString passwd = new SecureString(randomAlphaOfLength(10));
+    private static void testHasherSelfGenerated(Hasher hasher) {
+        SecureString passwd = new SecureString(randomAlphaOfLength(10).toCharArray());
         char[] hash = hasher.hash(passwd);
         assertTrue(hasher.verify(passwd, hash));
     }

+ 4 - 4
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AnalyzeTests.java

@@ -8,7 +8,6 @@ package org.elasticsearch.xpack.security.authz;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeAction;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.test.SecurityIntegTestCase;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 
 import java.util.Collections;
 
@@ -17,13 +16,14 @@ import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswo
 import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
 
 public class AnalyzeTests extends SecurityIntegTestCase {
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("test123".toCharArray())));
 
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString
+            ("test123".toCharArray())));
         return super.configUsers() +
-                "analyze_indices:" + USERS_PASSWD_HASHED + "\n" +
-                "analyze_cluster:" + USERS_PASSWD_HASHED + "\n";
+            "analyze_indices:" + usersPasswdHashed + "\n" +
+            "analyze_cluster:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 7 - 8
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java

@@ -16,7 +16,6 @@ import org.elasticsearch.client.Client;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.index.IndexNotFoundException;
 import org.elasticsearch.test.SecurityIntegTestCase;
-import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.junit.Before;
 
 import java.util.Collections;
@@ -31,16 +30,16 @@ import static org.hamcrest.CoreMatchers.equalTo;
 
 public class IndexAliasesTests extends SecurityIntegTestCase {
 
-    protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(new SecureString("test123".toCharArray())));
-
     @Override
     protected String configUsers() {
+        final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(new SecureString
+            ("test123".toCharArray())));
         return super.configUsers() +
-                "create_only:" + USERS_PASSWD_HASHED + "\n" +
-                "create_test_aliases_test:" + USERS_PASSWD_HASHED + "\n" +
-                "create_test_aliases_alias:" + USERS_PASSWD_HASHED + "\n" +
-                "create_test_aliases_test_alias:" + USERS_PASSWD_HASHED + "\n" +
-                "aliases_only:" + USERS_PASSWD_HASHED + "\n";
+            "create_only:" + usersPasswdHashed + "\n" +
+            "create_test_aliases_test:" + usersPasswdHashed + "\n" +
+            "create_test_aliases_alias:" + usersPasswdHashed + "\n" +
+            "create_test_aliases_test_alias:" + usersPasswdHashed + "\n" +
+            "aliases_only:" + usersPasswdHashed + "\n";
     }
 
     @Override

+ 3 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java

@@ -32,7 +32,9 @@ public class SecurityScrollTests extends SecurityIntegTestCase {
         securityClient().preparePutRole("scrollable")
                 .addIndices(new String[] { randomAlphaOfLengthBetween(4, 12) }, new String[] { "read" }, null, null, null)
                 .get();
-        securityClient().preparePutUser("other", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(), "scrollable").get();
+        securityClient().preparePutUser("other", SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(), getFastStoredHashAlgoForTests(),
+            "scrollable")
+            .get();
 
         final int numDocs = randomIntBetween(4, 16);
         IndexRequestBuilder[] docs = new IndexRequestBuilder[numDocs];

+ 2 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerIntegTests.java

@@ -48,7 +48,8 @@ public class SecurityIndexManagerIntegTests extends SecurityIntegTestCase {
                     for (int i = 0; i < numRequests; i++) {
                         requests.add(securityClient()
                                 .preparePutUser("user" + userNumber.getAndIncrement(), "password".toCharArray(),
-                                        randomAlphaOfLengthBetween(1, 16))
+                                    getFastStoredHashAlgoForTests(),
+                                    randomAlphaOfLengthBetween(1, 16))
                                 .request());
                     }
 

+ 6 - 1
x-pack/plugin/security/src/test/resources/org/elasticsearch/xpack/security/authc/file/users

@@ -5,4 +5,9 @@ plain:{plain}test123
 sha:{SHA}cojt0Pw//L6ToM8G41aOKFIWh7w=
 # this is a comment line
 # another comment line
-bcrypt10:$2y$10$FMhmFjwU5.qxQ/BsEciS9OqcJVkFMgXMo4uH5CelOR1j4N9zIv67e
+pbkdf2:{PBKDF2}10000$ekcItXk4jtK2bBjbVk0rZuWRjT0DoQqQJOIfyMeLIxg=$RA2/Nn1jRi8QskRS5IVotCV0FBO6M8DlNXC37GKa/8c=
+pbkdf2_1000:{PBKDF2}1000$32yPZSShxuKYAl47ip0g6VwbFrD8tvFJuQCoRPGhXC8=$cXAE1BkBXRmkv7pQA7fw4TZ1+rFWS2/nZGeA3kL1Eu8=
+pbkdf2_50000:{PBKDF2}50000$z1CLJt0MEFjkIK5iEfgvfnA6xq7lF25uasspsTKSo5Q=$XxCVLbaKDimOdyWgLCLJiyoiWpA/XDMe/xtVgn1r5Sg=
+bcrypt9:$2a$09$YhstxoAjO7M5MtAIFv7dVO70/pElJAYrzyumeCpLpZV2Gcz4J2/F.
+bcrypt10:$2a$10$cFxpMx6YDrH/PXwLpTlux.KVykN1TG2Pgdl5oJX5/G/KYp3G6jbFG
+bcrypt11:$2a$11$uxr7b0qgCrLV9VIz9XS7M.Eoc0gJRR60oV48UK5DKfLOp.9HjfYF2

+ 2 - 1
x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/role/CustomRolesProviderIT.java

@@ -16,6 +16,7 @@ import org.elasticsearch.example.realm.CustomRealm;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.test.ESIntegTestCase;
 import org.elasticsearch.xpack.core.XPackClientPlugin;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
 import org.elasticsearch.xpack.core.security.client.SecurityClient;
 
@@ -52,7 +53,7 @@ public class CustomRolesProviderIT extends ESIntegTestCase {
 
     public void setupTestUser(String role) {
         SecurityClient securityClient = new SecurityClient(client());
-        securityClient.preparePutUser(TEST_USER, TEST_PWD.toCharArray(), role).get();
+        securityClient.preparePutUser(TEST_USER, TEST_PWD.toCharArray(), Hasher.BCRYPT, role).get();
     }
 
     public void testAuthorizedCustomRoleSucceeds() throws Exception {

+ 2 - 1
x-pack/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java

@@ -20,6 +20,7 @@ import org.elasticsearch.env.Environment;
 import org.elasticsearch.xpack.core.security.action.role.GetRolesResponse;
 import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse;
 import org.elasticsearch.xpack.core.security.action.user.PutUserResponse;
+import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
 import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
 import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition;
@@ -46,7 +47,7 @@ public class MigrateToolIT extends MigrateToolTestCase {
         SecurityClient c = new SecurityClient(client);
 
         // Add an existing user so the tool will skip it
-        PutUserResponse pur = c.preparePutUser("existing", "s3kirt".toCharArray(), "role1", "user").get();
+        PutUserResponse pur = c.preparePutUser("existing", "s3kirt".toCharArray(), Hasher.BCRYPT, "role1", "user").get();
         assertTrue(pur.created());
     }
 

+ 10 - 6
x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java

@@ -54,6 +54,8 @@ public class UsersToolTests extends CommandTestCase {
     // settings used to create an Environment for tools
     Settings settings;
 
+    Hasher hasher;
+
     @BeforeClass
     public static void setupJimfs() throws IOException {
         String view = randomFrom("basic", "posix");
@@ -68,11 +70,12 @@ public class UsersToolTests extends CommandTestCase {
         IOUtils.rm(homeDir);
         confDir = homeDir.resolve("config");
         Files.createDirectories(confDir);
+        hasher = Hasher.resolve(randomFrom("bcrypt", "pbkdf2"));
         String defaultPassword = SecuritySettingsSourceField.TEST_PASSWORD;
         Files.write(confDir.resolve("users"), Arrays.asList(
-            "existing_user:" + new String(Hasher.BCRYPT.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)),
-            "existing_user2:" + new String(Hasher.BCRYPT.hash(new SecureString((defaultPassword + "2").toCharArray()))),
-            "existing_user3:" + new String(Hasher.BCRYPT.hash(new SecureString((defaultPassword + "3").toCharArray())))
+            "existing_user:" + new String(hasher.hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)),
+            "existing_user2:" + new String(hasher.hash(new SecureString((defaultPassword + "2").toCharArray()))),
+            "existing_user3:" + new String(hasher.hash(new SecureString((defaultPassword + "3").toCharArray())))
         ), StandardCharsets.UTF_8);
         Files.write(confDir.resolve("users_roles"), Arrays.asList(
             "test_admin:existing_user,existing_user2",
@@ -170,9 +173,10 @@ public class UsersToolTests extends CommandTestCase {
                 continue;
             }
             String gotHash = usernameHash[1];
-            SecureString expectedHash = new SecureString(password);
-            assertTrue("Expected hash " + expectedHash + " for password " + password + " but got " + gotHash,
-                       Hasher.BCRYPT.verify(expectedHash, gotHash.toCharArray()));
+            SecureString expectedHash = new SecureString(password.toCharArray());
+            // CommandTestCase#execute runs passwd with default settings, so bcrypt with cost of 10
+            Hasher bcryptHasher = Hasher.resolve("bcrypt");
+            assertTrue("Could not validate password for user", bcryptHasher.verify(expectedHash, gotHash.toCharArray()));
             return;
         }
         fail("Could not find username " + username + " in users file:\n" + lines.toString());