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>
This commit is contained in:
Lyudmila Fokina 2020-11-26 13:29:19 +01:00 committed by GitHub
parent 0f7bbed37e
commit c758dc7f4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 271 additions and 63 deletions

View file

@ -12,32 +12,53 @@ hashing algorithm by setting the <<static-cluster-setting,static>>
[[cache-hash-algo]] [[cache-hash-algo]]
.Cache hash algorithms .Cache hash algorithms
|======================= |=======================
| Algorithm | | | Description | Algorithm | | | Description
| `ssha256` | | | Uses a salted `sha-256` algorithm (default). | `ssha256` | | | Uses a salted `sha-256` algorithm (default).
| `md5` | | | Uses `MD5` algorithm. | `md5` | | | Uses `MD5` algorithm.
| `sha1` | | | Uses `SHA1` algorithm. | `sha1` | | | Uses `SHA1` algorithm.
| `bcrypt` | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds. | `bcrypt` | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds.
| `bcrypt4` | | | Uses `bcrypt` algorithm with salt generated in 16 rounds. | `bcrypt4` | | | Uses `bcrypt` algorithm with salt generated in 16 rounds.
| `bcrypt5` | | | Uses `bcrypt` algorithm with salt generated in 32 rounds. | `bcrypt5` | | | Uses `bcrypt` algorithm with salt generated in 32 rounds.
| `bcrypt6` | | | Uses `bcrypt` algorithm with salt generated in 64 rounds. | `bcrypt6` | | | Uses `bcrypt` algorithm with salt generated in 64 rounds.
| `bcrypt7` | | | Uses `bcrypt` algorithm with salt generated in 128 rounds. | `bcrypt7` | | | Uses `bcrypt` algorithm with salt generated in 128 rounds.
| `bcrypt8` | | | Uses `bcrypt` algorithm with salt generated in 256 rounds. | `bcrypt8` | | | Uses `bcrypt` algorithm with salt generated in 256 rounds.
| `bcrypt9` | | | Uses `bcrypt` algorithm with salt generated in 512 rounds. | `bcrypt9` | | | Uses `bcrypt` algorithm with salt generated in 512 rounds.
| `pbkdf2` | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a | `pbkdf2` | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
pseudorandom function using 10000 iterations. 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. 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. 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. 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. 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. 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. 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 memory. CAUTION: keeping clear text is considered insecure
and can be compromised at the OS level (for example through and can be compromised at the OS level (for example through
memory dumps and using `ptrace`). memory dumps and using `ptrace`).
@ -52,34 +73,55 @@ following:
[[password-hashing-algorithms]] [[password-hashing-algorithms]]
.Password hashing algorithms .Password hashing algorithms
|======================= |=======================
| Algorithm | | | Description | Algorithm | | | Description
| `bcrypt` | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds. (default) | `bcrypt` | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds. (default)
| `bcrypt4` | | | Uses `bcrypt` algorithm with salt generated in 16 rounds. | `bcrypt4` | | | Uses `bcrypt` algorithm with salt generated in 16 rounds.
| `bcrypt5` | | | Uses `bcrypt` algorithm with salt generated in 32 rounds. | `bcrypt5` | | | Uses `bcrypt` algorithm with salt generated in 32 rounds.
| `bcrypt6` | | | Uses `bcrypt` algorithm with salt generated in 64 rounds. | `bcrypt6` | | | Uses `bcrypt` algorithm with salt generated in 64 rounds.
| `bcrypt7` | | | Uses `bcrypt` algorithm with salt generated in 128 rounds. | `bcrypt7` | | | Uses `bcrypt` algorithm with salt generated in 128 rounds.
| `bcrypt8` | | | Uses `bcrypt` algorithm with salt generated in 256 rounds. | `bcrypt8` | | | Uses `bcrypt` algorithm with salt generated in 256 rounds.
| `bcrypt9` | | | Uses `bcrypt` algorithm with salt generated in 512 rounds. | `bcrypt9` | | | Uses `bcrypt` algorithm with salt generated in 512 rounds.
| `bcrypt10` | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds. | `bcrypt10` | | | Uses `bcrypt` algorithm with salt generated in 1024 rounds.
| `bcrypt11` | | | Uses `bcrypt` algorithm with salt generated in 2048 rounds. | `bcrypt11` | | | Uses `bcrypt` algorithm with salt generated in 2048 rounds.
| `bcrypt12` | | | Uses `bcrypt` algorithm with salt generated in 4096 rounds. | `bcrypt12` | | | Uses `bcrypt` algorithm with salt generated in 4096 rounds.
| `bcrypt13` | | | Uses `bcrypt` algorithm with salt generated in 8192 rounds. | `bcrypt13` | | | Uses `bcrypt` algorithm with salt generated in 8192 rounds.
| `bcrypt14` | | | Uses `bcrypt` algorithm with salt generated in 16384 rounds. | `bcrypt14` | | | Uses `bcrypt` algorithm with salt generated in 16384 rounds.
| `pbkdf2` | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a | `pbkdf2` | | | Uses `PBKDF2` key derivation function with `HMAC-SHA512` as a
pseudorandom function using 10000 iterations. 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. 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. 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. 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. 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. 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. 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.
|======================= |=======================

View file

@ -25,8 +25,8 @@ import java.util.Objects;
/** /**
* This MessageDigests class provides convenience methods for obtaining * This MessageDigests class provides convenience methods for obtaining
* thread local {@link MessageDigest} instances for MD5, SHA-1, and * thread local {@link MessageDigest} instances for MD5, SHA-1, SHA-256 and
* SHA-256 message digests. * SHA-512 message digests.
*/ */
public final class MessageDigests { 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> MD5_DIGEST = createThreadLocalMessageDigest("MD5");
private static final ThreadLocal<MessageDigest> SHA_1_DIGEST = createThreadLocalMessageDigest("SHA-1"); 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_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 * Returns a {@link MessageDigest} instance for MD5 digests; note
@ -80,6 +81,18 @@ public final class MessageDigests {
return get(SHA_256_DIGEST); 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) { private static MessageDigest get(ThreadLocal<MessageDigest> messageDigest) {
MessageDigest instance = messageDigest.get(); MessageDigest instance = messageDigest.get();
instance.reset(); instance.reset();

View file

@ -185,12 +185,12 @@ public enum Hasher {
PBKDF2() { PBKDF2() {
@Override @Override
public char[] hash(SecureString data) { public char[] hash(SecureString data) {
return getPbkdf2Hash(data, PBKDF2_DEFAULT_COST); return getPbkdf2Hash(data, PBKDF2_DEFAULT_COST, PBKDF2_PREFIX);
} }
@Override @Override
public boolean verify(SecureString data, char[] hash) { 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() { PBKDF2_1000() {
@Override @Override
public char[] hash(SecureString data) { public char[] hash(SecureString data) {
return getPbkdf2Hash(data, 1000); return getPbkdf2Hash(data, 1000, PBKDF2_PREFIX);
} }
@Override @Override
public boolean verify(SecureString data, char[] hash) { 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() { PBKDF2_10000() {
@Override @Override
public char[] hash(SecureString data) { public char[] hash(SecureString data) {
return getPbkdf2Hash(data, 10000); return getPbkdf2Hash(data, 10000, PBKDF2_PREFIX);
} }
@Override @Override
public boolean verify(SecureString data, char[] hash) { 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() { PBKDF2_50000() {
@Override @Override
public char[] hash(SecureString data) { public char[] hash(SecureString data) {
return getPbkdf2Hash(data, 50000); return getPbkdf2Hash(data, 50000, PBKDF2_PREFIX);
} }
@Override @Override
public boolean verify(SecureString data, char[] hash) { 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() { PBKDF2_100000() {
@Override @Override
public char[] hash(SecureString data) { public char[] hash(SecureString data) {
return getPbkdf2Hash(data, 100000); return getPbkdf2Hash(data, 100000, PBKDF2_PREFIX);
} }
@Override @Override
public boolean verify(SecureString data, char[] hash) { 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() { PBKDF2_500000() {
@Override @Override
public char[] hash(SecureString data) { public char[] hash(SecureString data) {
return getPbkdf2Hash(data, 500000); return getPbkdf2Hash(data, 500000, PBKDF2_PREFIX);
} }
@Override @Override
public boolean verify(SecureString data, char[] hash) { 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() { PBKDF2_1000000() {
@Override @Override
public char[] hash(SecureString data) { public char[] hash(SecureString data) {
return getPbkdf2Hash(data, 1000000); return getPbkdf2Hash(data, 1000000, PBKDF2_PREFIX);
} }
@Override @Override
public boolean verify(SecureString data, char[] hash) { 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 MD5_PREFIX = "{MD5}";
private static final String SSHA256_PREFIX = "{SSHA256}"; private static final String SSHA256_PREFIX = "{SSHA256}";
private static final String PBKDF2_PREFIX = "{PBKDF2}"; 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_DEFAULT_COST = 10000;
private static final int PBKDF2_KEY_LENGTH = 256; private static final int PBKDF2_KEY_LENGTH = 256;
private static final int BCRYPT_DEFAULT_COST = 10; private static final int BCRYPT_DEFAULT_COST = 10;
@ -442,6 +534,20 @@ public enum Hasher {
return PBKDF2_500000; return PBKDF2_500000;
case "pbkdf2_1000000": case "pbkdf2_1000000":
return 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": case "sha1":
return SHA1; return SHA1;
case "md5": case "md5":
@ -468,6 +574,9 @@ public enum Hasher {
if (CharArrays.charsBeginsWith(BCRYPT_PREFIX, hash)) { if (CharArrays.charsBeginsWith(BCRYPT_PREFIX, hash)) {
int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, BCRYPT_PREFIX.length(), hash.length - 54))); 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); 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)) { } else if (CharArrays.charsBeginsWith(PBKDF2_PREFIX, hash)) {
int cost = Integer.parseInt(new String(Arrays.copyOfRange(hash, PBKDF2_PREFIX.length(), hash.length - 90))); 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); return cost == PBKDF2_DEFAULT_COST ? Hasher.PBKDF2 : resolve("pbkdf2_" + cost);
@ -497,12 +606,12 @@ public enum Hasher {
return hasher.verify(data, hash); return hasher.verify(data, hash);
} }
private static char[] getPbkdf2Hash(SecureString data, int cost) { private static char[] getPbkdf2Hash(SecureString data, int cost, String prefix) {
try { try {
// Base64 string length : (4*(n/3)) rounded up to the next multiple of 4 because of padding. // 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. // 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); CharBuffer result = CharBuffer.allocate(prefix.length() + String.valueOf(cost).length() + 2 + 44 + 44);
result.put(PBKDF2_PREFIX); result.put(prefix);
result.put(String.valueOf(cost)); result.put(String.valueOf(cost));
result.put("$"); result.put("$");
byte[] salt = generateSalt(32); 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. // 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 // n is 32 (PBKDF2_KEY_LENGTH in bytes), so tokenLength is 44
final int tokenLength = 44; final int tokenLength = 44;
@ -525,12 +634,12 @@ public enum Hasher {
char[] saltChars = null; char[] saltChars = null;
char[] computedPwdHash = null; char[] computedPwdHash = null;
try { try {
if (CharArrays.charsBeginsWith(PBKDF2_PREFIX, hash) == false) { if (CharArrays.charsBeginsWith(prefix, hash) == false) {
return false; return false;
} }
hashChars = Arrays.copyOfRange(hash, hash.length - tokenLength, hash.length); hashChars = Arrays.copyOfRange(hash, hash.length - tokenLength, hash.length);
saltChars = Arrays.copyOfRange(hash, hash.length - (2 * tokenLength + 1), hash.length - (tokenLength + 1)); 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"); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHMACSHA512");
PBEKeySpec keySpec = new PBEKeySpec(data.getChars(), Base64.getDecoder().decode(CharArrays.toUtf8Bytes(saltChars)), PBEKeySpec keySpec = new PBEKeySpec(data.getChars(), Base64.getDecoder().decode(CharArrays.toUtf8Bytes(saltChars)),
cost, PBKDF2_KEY_LENGTH); cost, PBKDF2_KEY_LENGTH);
@ -597,4 +706,10 @@ public enum Hasher {
SECURE_RANDOM.nextBytes(salt); SECURE_RANDOM.nextBytes(salt);
return salt; return salt;
} }
private static char[] hashSha512(SecureString text) {
MessageDigest md = MessageDigests.sha512();
md.update(CharArrays.toUtf8Bytes(text.getChars()));
return MessageDigests.toHexCharArray(md.digest());
}
} }

View file

@ -36,6 +36,13 @@ public class HasherTests extends ESTestCase {
testHasherSelfGenerated(Hasher.PBKDF2_100000); testHasherSelfGenerated(Hasher.PBKDF2_100000);
testHasherSelfGenerated(Hasher.PBKDF2_500000); testHasherSelfGenerated(Hasher.PBKDF2_500000);
testHasherSelfGenerated(Hasher.PBKDF2_1000000); 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 { 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_100000"), sameInstance(Hasher.PBKDF2_100000));
assertThat(Hasher.resolve("pbkdf2_500000"), sameInstance(Hasher.PBKDF2_500000)); assertThat(Hasher.resolve("pbkdf2_500000"), sameInstance(Hasher.PBKDF2_500000));
assertThat(Hasher.resolve("pbkdf2_1000000"), sameInstance(Hasher.PBKDF2_1000000)); 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("sha1"), sameInstance(Hasher.SHA1));
assertThat(Hasher.resolve("md5"), sameInstance(Hasher.MD5)); assertThat(Hasher.resolve("md5"), sameInstance(Hasher.MD5));
assertThat(Hasher.resolve("ssha256"), sameInstance(Hasher.SSHA256)); assertThat(Hasher.resolve("ssha256"), sameInstance(Hasher.SSHA256));
@ -132,11 +146,35 @@ public class HasherTests extends ESTestCase {
assertThat(Hasher.resolveFromHash( assertThat(Hasher.resolveFromHash(
"{PBKDF2}1000000$UuyhtjDEzWmE2wyY80akZKPWWpy2r2X50so41YML82U=$WFasYLelqbjQwt3EqFlUcwHiC38EZC45Iu/Iz0xL1GQ=".toCharArray()), "{PBKDF2}1000000$UuyhtjDEzWmE2wyY80akZKPWWpy2r2X50so41YML82U=$WFasYLelqbjQwt3EqFlUcwHiC38EZC45Iu/Iz0xL1GQ=".toCharArray()),
sameInstance(Hasher.PBKDF2_1000000)); 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)); assertThat(Hasher.resolveFromHash("notavalidhashformat".toCharArray()), sameInstance(Hasher.NOOP));
} }
private static void testHasherSelfGenerated(Hasher hasher) { 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); char[] hash = hasher.hash(passwd);
assertTrue(hasher.verify(passwd, hash)); assertTrue(hasher.verify(passwd, hash));
} }

View file

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