Browse Source

Introduce an additional hasher (PBKDF2_STRETCH) (#65328)

* Introduce an additional hasher that is PBKDF2 but pads the input to > 14 chars before hashing to comply with FIPS Approve Only mode

* Introduce an additional hasher that is PBKDF2 but pads the input to > 14 chars before hashing to comply with FIPS Approve Only mode

* Addressing the PR feedback
adding doc changes

* Renaming the hash function + rephrasing the doc descriptions

* Removing leftover from the doc

* Return HexCharArray instead of Base64 encoding and avoid intermediate
String

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Lyudmila Fokina 4 years ago
parent
commit
c758dc7f4a

+ 81 - 39
docs/reference/settings/security-hash-settings.asciidoc

@@ -12,32 +12,53 @@ hashing algorithm by setting the <<static-cluster-setting,static>>
 [[cache-hash-algo]]
 .Cache hash algorithms
 |=======================
-| Algorithm           | | | Description
-| `ssha256`           | | | Uses a salted `sha-256` algorithm (default).
-| `md5`               | | | Uses `MD5` algorithm.
-| `sha1`              | | | Uses `SHA1` algorithm.
-| `bcrypt`            | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds.
-| `bcrypt4`           | | | Uses `bcrypt` algorithm with salt generated in 16 rounds.
-| `bcrypt5`           | | | Uses `bcrypt` algorithm with salt generated in 32 rounds.
-| `bcrypt6`           | | | Uses `bcrypt` algorithm with salt generated in 64 rounds.
-| `bcrypt7`           | | | Uses `bcrypt` algorithm with salt generated in 128 rounds.
-| `bcrypt8`           | | | Uses `bcrypt` algorithm with salt generated in 256 rounds.
-| `bcrypt9`           | | | Uses `bcrypt` algorithm with salt generated in 512 rounds.
-| `pbkdf2`            | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| Algorithm               | | | Description
+| `ssha256`               | | | Uses a salted `sha-256` algorithm (default).
+| `md5`                   | | | Uses `MD5` algorithm.
+| `sha1`                  | | | Uses `SHA1` algorithm.
+| `bcrypt`                | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds.
+| `bcrypt4`               | | | Uses `bcrypt` algorithm with salt generated in 16 rounds.
+| `bcrypt5`               | | | Uses `bcrypt` algorithm with salt generated in 32 rounds.
+| `bcrypt6`               | | | Uses `bcrypt` algorithm with salt generated in 64 rounds.
+| `bcrypt7`               | | | Uses `bcrypt` algorithm with salt generated in 128 rounds.
+| `bcrypt8`               | | | Uses `bcrypt` algorithm with salt generated in 256 rounds.
+| `bcrypt9`               | | | Uses `bcrypt` algorithm with salt generated in 512 rounds.
+| `pbkdf2`                | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 10000 iterations.
-| `pbkdf2_1000`       | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_1000`           | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 1000 iterations.
-| `pbkdf2_10000`      | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_10000`          | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 10000 iterations.
-| `pbkdf2_50000`      | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_50000`          | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 50000 iterations.
-| `pbkdf2_100000`     | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_100000`         | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 100000 iterations.
-| `pbkdf2_500000`     | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_500000`         | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                               pseudorandom function using 500000 iterations.
-| `pbkdf2_1000000`    | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_1000000`        | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 1000000 iterations.
-| `noop`,`clear_text` | | | Doesn't hash the credentials and keeps it in clear text in
+| `pbkdf2_stretch`        | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 10000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_1000`   | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 1000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_10000`  | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 10000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_50000`  | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 50000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_100000` | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 100000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_500000` | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 500000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_1000000`| | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 1000000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `noop`,`clear_text`     | | | Doesn't hash the credentials and keeps it in clear text in
                             memory. CAUTION: keeping clear text is considered insecure
                             and can be compromised at the OS level (for example through
                             memory dumps and using `ptrace`).
@@ -52,34 +73,55 @@ following:
 [[password-hashing-algorithms]]
 .Password hashing algorithms
 |=======================
-| Algorithm           | | | Description
+| Algorithm               | | | Description
 
-| `bcrypt`            | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds. (default)
-| `bcrypt4`           | | | Uses `bcrypt` algorithm with salt generated in 16 rounds.
-| `bcrypt5`           | | | Uses `bcrypt` algorithm with salt generated in 32 rounds.
-| `bcrypt6`           | | | Uses `bcrypt` algorithm with salt generated in 64 rounds.
-| `bcrypt7`           | | | Uses `bcrypt` algorithm with salt generated in 128 rounds.
-| `bcrypt8`           | | | Uses `bcrypt` algorithm with salt generated in 256 rounds.
-| `bcrypt9`           | | | Uses `bcrypt` algorithm with salt generated in 512 rounds.
-| `bcrypt10`          | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds.
-| `bcrypt11`          | | | Uses `bcrypt` algorithm with salt generated in 2048 rounds.
-| `bcrypt12`          | | | Uses `bcrypt` algorithm with salt generated in 4096 rounds.
-| `bcrypt13`          | | | Uses `bcrypt` algorithm with salt generated in 8192 rounds.
-| `bcrypt14`          | | | Uses `bcrypt` algorithm with salt generated in 16384 rounds.
-| `pbkdf2`            | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `bcrypt`                | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds. (default)
+| `bcrypt4`               | | | Uses `bcrypt` algorithm with salt generated in 16 rounds.
+| `bcrypt5`               | | | Uses `bcrypt` algorithm with salt generated in 32 rounds.
+| `bcrypt6`               | | | Uses `bcrypt` algorithm with salt generated in 64 rounds.
+| `bcrypt7`               | | | Uses `bcrypt` algorithm with salt generated in 128 rounds.
+| `bcrypt8`               | | | Uses `bcrypt` algorithm with salt generated in 256 rounds.
+| `bcrypt9`               | | | Uses `bcrypt` algorithm with salt generated in 512 rounds.
+| `bcrypt10`              | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds.
+| `bcrypt11`              | | | Uses `bcrypt` algorithm with salt generated in 2048 rounds.
+| `bcrypt12`              | | | Uses `bcrypt` algorithm with salt generated in 4096 rounds.
+| `bcrypt13`              | | | Uses `bcrypt` algorithm with salt generated in 8192 rounds.
+| `bcrypt14`              | | | Uses `bcrypt` algorithm with salt generated in 16384 rounds.
+| `pbkdf2`                | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 10000 iterations.
-| `pbkdf2_1000`       | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_1000`           | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 1000 iterations.
-| `pbkdf2_10000`      | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_10000`          | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 10000 iterations.
-| `pbkdf2_50000`      | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_50000`          | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 50000 iterations.
-| `pbkdf2_100000`     | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_100000`         | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 100000 iterations.
-| `pbkdf2_500000`     | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_500000`         | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                               pseudorandom function using 500000 iterations.
-| `pbkdf2_1000000`    | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+| `pbkdf2_1000000`        | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
                              pseudorandom function using 1000000 iterations.
+| `pbkdf2_stretch`        | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 10000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_1000`   | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 1000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_10000`  | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 10000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_50000`  | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 50000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_100000` | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 100000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_500000` | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 500000 iterations, after hashing the
+                             initial input with SHA512 first.
+| `pbkdf2_stretch_1000000`| | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
+                             pseudorandom function using 1000000 iterations, after hashing the
+                             initial input with SHA512 first.
 |=======================
 
 

+ 15 - 2
server/src/main/java/org/elasticsearch/common/hash/MessageDigests.java

@@ -25,8 +25,8 @@ import java.util.Objects;
 
 /**
  * This MessageDigests class provides convenience methods for obtaining
- * thread local {@link MessageDigest} instances for MD5, SHA-1, and
- * SHA-256 message digests.
+ * thread local {@link MessageDigest} instances for MD5, SHA-1, SHA-256 and
+ * SHA-512 message digests.
  */
 public final class MessageDigests {
 
@@ -43,6 +43,7 @@ public final class MessageDigests {
     private static final ThreadLocal<MessageDigest> MD5_DIGEST = createThreadLocalMessageDigest("MD5");
     private static final ThreadLocal<MessageDigest> SHA_1_DIGEST = createThreadLocalMessageDigest("SHA-1");
     private static final ThreadLocal<MessageDigest> SHA_256_DIGEST = createThreadLocalMessageDigest("SHA-256");
+    private static final ThreadLocal<MessageDigest> SHA_512_DIGEST = createThreadLocalMessageDigest("SHA-512");
 
     /**
      * Returns a {@link MessageDigest} instance for MD5 digests; note
@@ -80,6 +81,18 @@ public final class MessageDigests {
         return get(SHA_256_DIGEST);
     }
 
+    /**
+     * Returns a {@link MessageDigest} instance for SHA-512 digests;
+     * note that the instance returned is thread local and must not be
+     * shared amongst threads.
+     *
+     * @return a thread local {@link MessageDigest} instance that
+     * provides SHA-512 message digest functionality.
+     */
+    public static MessageDigest sha512() {
+        return get(SHA_512_DIGEST);
+    }
+
     private static MessageDigest get(ThreadLocal<MessageDigest> messageDigest) {
         MessageDigest instance = messageDigest.get();
         instance.reset();

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

@@ -185,12 +185,12 @@ public enum Hasher {
     PBKDF2() {
         @Override
         public char[] hash(SecureString data) {
-            return getPbkdf2Hash(data, PBKDF2_DEFAULT_COST);
+            return getPbkdf2Hash(data, PBKDF2_DEFAULT_COST, PBKDF2_PREFIX);
         }
 
         @Override
         public boolean verify(SecureString data, char[] hash) {
-            return verifyPbkdf2Hash(data, hash);
+            return verifyPbkdf2Hash(data, hash, PBKDF2_PREFIX);
         }
 
     },
@@ -198,12 +198,12 @@ public enum Hasher {
     PBKDF2_1000() {
         @Override
         public char[] hash(SecureString data) {
-            return getPbkdf2Hash(data, 1000);
+            return getPbkdf2Hash(data, 1000, PBKDF2_PREFIX);
         }
 
         @Override
         public boolean verify(SecureString data, char[] hash) {
-            return verifyPbkdf2Hash(data, hash);
+            return verifyPbkdf2Hash(data, hash, PBKDF2_PREFIX);
         }
 
     },
@@ -211,12 +211,12 @@ public enum Hasher {
     PBKDF2_10000() {
         @Override
         public char[] hash(SecureString data) {
-            return getPbkdf2Hash(data, 10000);
+            return getPbkdf2Hash(data, 10000, PBKDF2_PREFIX);
         }
 
         @Override
         public boolean verify(SecureString data, char[] hash) {
-            return verifyPbkdf2Hash(data, hash);
+            return verifyPbkdf2Hash(data, hash, PBKDF2_PREFIX);
         }
 
     },
@@ -224,12 +224,12 @@ public enum Hasher {
     PBKDF2_50000() {
         @Override
         public char[] hash(SecureString data) {
-            return getPbkdf2Hash(data, 50000);
+            return getPbkdf2Hash(data, 50000, PBKDF2_PREFIX);
         }
 
         @Override
         public boolean verify(SecureString data, char[] hash) {
-            return verifyPbkdf2Hash(data, hash);
+            return verifyPbkdf2Hash(data, hash, PBKDF2_PREFIX);
         }
 
     },
@@ -237,12 +237,12 @@ public enum Hasher {
     PBKDF2_100000() {
         @Override
         public char[] hash(SecureString data) {
-            return getPbkdf2Hash(data, 100000);
+            return getPbkdf2Hash(data, 100000, PBKDF2_PREFIX);
         }
 
         @Override
         public boolean verify(SecureString data, char[] hash) {
-            return verifyPbkdf2Hash(data, hash);
+            return verifyPbkdf2Hash(data, hash, PBKDF2_PREFIX);
         }
 
     },
@@ -250,12 +250,12 @@ public enum Hasher {
     PBKDF2_500000() {
         @Override
         public char[] hash(SecureString data) {
-            return getPbkdf2Hash(data, 500000);
+            return getPbkdf2Hash(data, 500000, PBKDF2_PREFIX);
         }
 
         @Override
         public boolean verify(SecureString data, char[] hash) {
-            return verifyPbkdf2Hash(data, hash);
+            return verifyPbkdf2Hash(data, hash, PBKDF2_PREFIX);
         }
 
     },
@@ -263,12 +263,103 @@ public enum Hasher {
     PBKDF2_1000000() {
         @Override
         public char[] hash(SecureString data) {
-            return getPbkdf2Hash(data, 1000000);
+            return getPbkdf2Hash(data, 1000000, PBKDF2_PREFIX);
         }
 
         @Override
         public boolean verify(SecureString data, char[] hash) {
-            return verifyPbkdf2Hash(data, hash);
+            return verifyPbkdf2Hash(data, hash, PBKDF2_PREFIX);
+        }
+
+    },
+
+    PBKDF2_STRETCH() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(new SecureString(hashSha512(data)), PBKDF2_DEFAULT_COST, PBKDF2_STRETCH_PREFIX);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(new SecureString(hashSha512(data)), hash, PBKDF2_STRETCH_PREFIX);
+        }
+
+    },
+
+    PBKDF2_STRETCH_1000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(new SecureString(hashSha512(data)), 1000, PBKDF2_STRETCH_PREFIX);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(new SecureString(hashSha512(data)), hash, PBKDF2_STRETCH_PREFIX);
+        }
+
+    },
+
+    PBKDF2_STRETCH_10000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(new SecureString(hashSha512(data)), 10000, PBKDF2_STRETCH_PREFIX);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(new SecureString(hashSha512(data)), hash, PBKDF2_STRETCH_PREFIX);
+        }
+
+    },
+
+    PBKDF2_STRETCH_50000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(new SecureString(hashSha512(data)), 50000, PBKDF2_STRETCH_PREFIX);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(new SecureString(hashSha512(data)), hash, PBKDF2_STRETCH_PREFIX);
+        }
+
+    },
+
+    PBKDF2_STRETCH_100000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(new SecureString(hashSha512(data)), 100000, PBKDF2_STRETCH_PREFIX);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(new SecureString(hashSha512(data)), hash, PBKDF2_STRETCH_PREFIX);
+        }
+
+    },
+
+    PBKDF2_STRETCH_500000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(new SecureString(hashSha512(data)), 500000, PBKDF2_STRETCH_PREFIX);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(new SecureString(hashSha512(data)), hash, PBKDF2_STRETCH_PREFIX);
+        }
+
+    },
+
+    PBKDF2_STRETCH_1000000() {
+        @Override
+        public char[] hash(SecureString data) {
+            return getPbkdf2Hash(new SecureString(hashSha512(data)), 1000000, PBKDF2_STRETCH_PREFIX);
+        }
+
+        @Override
+        public boolean verify(SecureString data, char[] hash) {
+            return verifyPbkdf2Hash(new SecureString(hashSha512(data)), hash, PBKDF2_STRETCH_PREFIX);
         }
 
     },
@@ -388,6 +479,7 @@ public enum Hasher {
     private static final String MD5_PREFIX = "{MD5}";
     private static final String SSHA256_PREFIX = "{SSHA256}";
     private static final String PBKDF2_PREFIX = "{PBKDF2}";
+    private static final String PBKDF2_STRETCH_PREFIX = "{PBKDF2_STRETCH}";
     private static final int PBKDF2_DEFAULT_COST = 10000;
     private static final int PBKDF2_KEY_LENGTH = 256;
     private static final int BCRYPT_DEFAULT_COST = 10;
@@ -442,6 +534,20 @@ public enum Hasher {
                 return PBKDF2_500000;
             case "pbkdf2_1000000":
                 return PBKDF2_1000000;
+            case "pbkdf2_stretch":
+                return PBKDF2_STRETCH;
+            case "pbkdf2_stretch_1000":
+                return PBKDF2_STRETCH_1000;
+            case "pbkdf2_stretch_10000":
+                return PBKDF2_STRETCH_10000;
+            case "pbkdf2_stretch_50000":
+                return PBKDF2_STRETCH_50000;
+            case "pbkdf2_stretch_100000":
+                return PBKDF2_STRETCH_100000;
+            case "pbkdf2_stretch_500000":
+                return PBKDF2_STRETCH_500000;
+            case "pbkdf2_stretch_1000000":
+                return PBKDF2_STRETCH_1000000;
             case "sha1":
                 return SHA1;
             case "md5":
@@ -468,6 +574,9 @@ public enum Hasher {
         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_STRETCH_PREFIX, hash)) {
+            int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, PBKDF2_STRETCH_PREFIX.length(), hash.length - 90)));
+            return cost == PBKDF2_DEFAULT_COST ? Hasher.PBKDF2_STRETCH : resolve("pbkdf2_stretch_" + 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);
@@ -497,12 +606,12 @@ public enum Hasher {
         return hasher.verify(data, hash);
     }
 
-    private static char[] getPbkdf2Hash(SecureString data, int cost) {
+    private static char[] getPbkdf2Hash(SecureString data, int cost, String prefix) {
         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);
+            CharBuffer result = CharBuffer.allocate(prefix.length() + String.valueOf(cost).length() + 2 + 44 + 44);
+            result.put(prefix);
             result.put(String.valueOf(cost));
             result.put("$");
             byte[] salt = generateSalt(32);
@@ -517,7 +626,7 @@ public enum Hasher {
         }
     }
 
-    private static boolean verifyPbkdf2Hash(SecureString data, char[] hash) {
+    private static boolean verifyPbkdf2Hash(SecureString data, char[] hash, String prefix) {
         // 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;
@@ -525,12 +634,12 @@ public enum Hasher {
         char[] saltChars = null;
         char[] computedPwdHash = null;
         try {
-            if (CharArrays.charsBeginsWith(PBKDF2_PREFIX, hash) == false) {
+            if (CharArrays.charsBeginsWith(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))));
+            int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, 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);
@@ -597,4 +706,10 @@ public enum Hasher {
         SECURE_RANDOM.nextBytes(salt);
         return salt;
     }
+
+    private static char[] hashSha512(SecureString text) {
+        MessageDigest md = MessageDigests.sha512();
+        md.update(CharArrays.toUtf8Bytes(text.getChars()));
+        return MessageDigests.toHexCharArray(md.digest());
+    }
 }

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

@@ -36,6 +36,13 @@ public class HasherTests extends ESTestCase {
         testHasherSelfGenerated(Hasher.PBKDF2_100000);
         testHasherSelfGenerated(Hasher.PBKDF2_500000);
         testHasherSelfGenerated(Hasher.PBKDF2_1000000);
+        testHasherSelfGenerated(Hasher.PBKDF2_STRETCH);
+        testHasherSelfGenerated(Hasher.PBKDF2_STRETCH_1000);
+        testHasherSelfGenerated(Hasher.PBKDF2_STRETCH_10000);
+        testHasherSelfGenerated(Hasher.PBKDF2_STRETCH_50000);
+        testHasherSelfGenerated(Hasher.PBKDF2_STRETCH_100000);
+        testHasherSelfGenerated(Hasher.PBKDF2_STRETCH_500000);
+        testHasherSelfGenerated(Hasher.PBKDF2_STRETCH_1000000);
     }
 
     public void testMd5SelfGenerated() throws Exception {
@@ -78,6 +85,13 @@ public class HasherTests extends ESTestCase {
         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("pbkdf2_stretch"), sameInstance(Hasher.PBKDF2_STRETCH));
+        assertThat(Hasher.resolve("pbkdf2_stretch_1000"), sameInstance(Hasher.PBKDF2_STRETCH_1000));
+        assertThat(Hasher.resolve("pbkdf2_stretch_10000"), sameInstance(Hasher.PBKDF2_STRETCH_10000));
+        assertThat(Hasher.resolve("pbkdf2_stretch_50000"), sameInstance(Hasher.PBKDF2_STRETCH_50000));
+        assertThat(Hasher.resolve("pbkdf2_stretch_100000"), sameInstance(Hasher.PBKDF2_STRETCH_100000));
+        assertThat(Hasher.resolve("pbkdf2_stretch_500000"), sameInstance(Hasher.PBKDF2_STRETCH_500000));
+        assertThat(Hasher.resolve("pbkdf2_stretch_1000000"), sameInstance(Hasher.PBKDF2_STRETCH_1000000));
         assertThat(Hasher.resolve("sha1"), sameInstance(Hasher.SHA1));
         assertThat(Hasher.resolve("md5"), sameInstance(Hasher.MD5));
         assertThat(Hasher.resolve("ssha256"), sameInstance(Hasher.SSHA256));
@@ -132,11 +146,35 @@ public class HasherTests extends ESTestCase {
         assertThat(Hasher.resolveFromHash(
             "{PBKDF2}1000000$UuyhtjDEzWmE2wyY80akZKPWWpy2r2X50so41YML82U=$WFasYLelqbjQwt3EqFlUcwHiC38EZC45Iu/Iz0xL1GQ=".toCharArray()),
             sameInstance(Hasher.PBKDF2_1000000));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2_STRETCH}1000$sTyix9e0zNINzq2aDZ+GD5+QlO94xVyf/bv4pWNhBxo=$4KuzGPy9HXnhY3ANHn8rcIRQuJHPB6cEtLwnOhDI5d4="
+                .toCharArray()),
+            sameInstance(Hasher.PBKDF2_STRETCH_1000));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2_STRETCH}10000$8M9+Ww0xkdY250CROEutsd8UP6CrJESw7ZAFu1NGORo=$ai0gxBPtHTfZU/nbNGwL5zjC+eo2/ANQM17L/tllVeo="
+                .toCharArray()),
+            sameInstance(Hasher.PBKDF2_STRETCH));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2_STRETCH}50000$uupwXiq8W0+jrLtC3/aqzuvyZlRarlmx1+CQGEnomlk=$by8q/+oRPPWwDE6an7B9/ndz7UZ1UQpaGY4CGurtPTI="
+                .toCharArray()),
+            sameInstance(Hasher.PBKDF2_STRETCH_50000));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2_STRETCH}100000$E9VqtV76PcrQuCZ6wOMMNvs4CMPcANTpzRw8Wjd24PU=$j56uKUvwbvmgQgNFkbV7SRQVZ2QOarokAgBeA8xcFD8="
+                .toCharArray()),
+            sameInstance(Hasher.PBKDF2_STRETCH_100000));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2_STRETCH}500000$4dpTEbu4jfjhDOjWY6xdsnxuQs4dg4QbNzZJ0Z1Tm4s=$Us/yrlCxVaW7mz0go1qIygFqGgcfUMgCZfIl2AvI4I8="
+                .toCharArray()),
+            sameInstance(Hasher.PBKDF2_STRETCH_500000));
+        assertThat(Hasher.resolveFromHash(
+            "{PBKDF2_STRETCH}1000000$eKeQvMztiIcqBynTNDFBseOBww3GBpHDZI6EPPVHYUw=$4587yrxUa02RZ1jeW1WOaMjRn5qT9iQ5/DIHk0nW2bE="
+                .toCharArray()),
+            sameInstance(Hasher.PBKDF2_STRETCH_1000000));
         assertThat(Hasher.resolveFromHash("notavalidhashformat".toCharArray()), sameInstance(Hasher.NOOP));
     }
 
     private static void testHasherSelfGenerated(Hasher hasher) {
-        SecureString passwd = new SecureString(randomAlphaOfLength(10).toCharArray());
+        SecureString passwd = new SecureString(randomAlphaOfLength(between(6, 15)).toCharArray());
         char[] hash = hasher.hash(passwd);
         assertTrue(hasher.verify(passwd, hash));
     }

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

@@ -70,7 +70,7 @@ public class UsersToolTests extends CommandTestCase {
         IOUtils.rm(homeDir);
         confDir = homeDir.resolve("config");
         Files.createDirectories(confDir);
-        hasher = inFipsJvm() ? randomFrom(Hasher.PBKDF2, Hasher.PBKDF2_1000)
+        hasher = inFipsJvm() ? randomFrom(Hasher.PBKDF2, Hasher.PBKDF2_1000, Hasher.PBKDF2_STRETCH)
             : randomFrom(Hasher.PBKDF2_1000, Hasher.PBKDF2, Hasher.BCRYPT, Hasher.BCRYPT9);
         String defaultPassword = SecuritySettingsSourceField.TEST_PASSWORD;
         Files.write(confDir.resolve("users"), Arrays.asList(