fix salt problem for yescrypt hashes in HashGenerator #96
@ -31,22 +31,37 @@ public final class HashGenerator {
|
|||||||
|
|
||||||
public enum Algorithm {
|
public enum Algorithm {
|
||||||
LINUX_SHA512(LinuxEtcShadowHashGenerator::hash, "6"),
|
LINUX_SHA512(LinuxEtcShadowHashGenerator::hash, "6"),
|
||||||
LINUX_YESCRYPT(LinuxEtcShadowHashGenerator::hash, "y"),
|
LINUX_YESCRYPT(LinuxEtcShadowHashGenerator::hash, "y", "j9T$") {
|
||||||
|
@Override
|
||||||
|
String enrichedSalt(final String salt) {
|
||||||
|
return prefix + "$" + (salt.startsWith(optionalParam) ? salt : optionalParam + salt);
|
||||||
|
}
|
||||||
|
},
|
||||||
MYSQL_NATIVE(MySQLNativePasswordHashGenerator::hash, "*"),
|
MYSQL_NATIVE(MySQLNativePasswordHashGenerator::hash, "*"),
|
||||||
SCRAM_SHA256(PostgreSQLScramSHA256::hash, "SCRAM-SHA-256");
|
SCRAM_SHA256(PostgreSQLScramSHA256::hash, "SCRAM-SHA-256");
|
||||||
|
|
||||||
final BiFunction<HashGenerator, String, String> implementation;
|
final BiFunction<HashGenerator, String, String> implementation;
|
||||||
final String prefix;
|
final String prefix;
|
||||||
|
final String optionalParam;
|
||||||
|
|
||||||
Algorithm(BiFunction<HashGenerator, String, String> implementation, final String prefix) {
|
Algorithm(BiFunction<HashGenerator, String, String> implementation, final String prefix, final String optionalParam) {
|
||||||
this.implementation = implementation;
|
this.implementation = implementation;
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
|
this.optionalParam = optionalParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
Algorithm(BiFunction<HashGenerator, String, String> implementation, final String prefix) {
|
||||||
|
this(implementation, prefix, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Algorithm byPrefix(final String prefix) {
|
static Algorithm byPrefix(final String prefix) {
|
||||||
return Arrays.stream(Algorithm.values()).filter(a -> a.prefix.equals(prefix)).findAny()
|
return Arrays.stream(Algorithm.values()).filter(a -> a.prefix.equals(prefix)).findAny()
|
||||||
.orElseThrow(() -> new IllegalArgumentException("unknown hash algorithm: '" + prefix + "'"));
|
.orElseThrow(() -> new IllegalArgumentException("unknown hash algorithm: '" + prefix + "'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String enrichedSalt(final String salt) {
|
||||||
|
return prefix + "$" + salt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Algorithm algorithm;
|
private final Algorithm algorithm;
|
||||||
@ -60,7 +75,7 @@ public final class HashGenerator {
|
|||||||
this.algorithm = algorithm;
|
this.algorithm = algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void enableChouldBeHash(final boolean enable) {
|
public static void enableCouldBeHash(final boolean enable) {
|
||||||
couldBeHashEnabled = enable;
|
couldBeHashEnabled = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +88,11 @@ public final class HashGenerator {
|
|||||||
throw new IllegalStateException("no password given");
|
throw new IllegalStateException("no password given");
|
||||||
}
|
}
|
||||||
|
|
||||||
return algorithm.implementation.apply(this, plaintextPassword);
|
final var hash = algorithm.implementation.apply(this, plaintextPassword);
|
||||||
|
if (hash.length() < plaintextPassword.length()) {
|
||||||
|
throw new AssertionError("generated hash too short: " + hash);
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String hashIfNotYetHashed(final String plaintextPasswordOrHash) {
|
public String hashIfNotYetHashed(final String plaintextPasswordOrHash) {
|
||||||
@ -102,4 +121,10 @@ public final class HashGenerator {
|
|||||||
}
|
}
|
||||||
return withSalt(stringBuilder.toString());
|
return withSalt(stringBuilder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(
|
||||||
|
HashGenerator.using(Algorithm.LINUX_YESCRYPT).withRandomSalt().hash("my plaintext domain transfer passphrase")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ public class LinuxEtcShadowHashGenerator {
|
|||||||
throw new IllegalStateException("no salt given");
|
throw new IllegalStateException("no salt given");
|
||||||
}
|
}
|
||||||
|
|
||||||
return NativeCryptLibrary.INSTANCE.crypt(payload, "$" + generator.getAlgorithm().prefix + "$" + generator.getSalt());
|
return NativeCryptLibrary.INSTANCE.crypt(payload, "$" + generator.getAlgorithm().enrichedSalt(generator.getSalt()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void verify(final String givenHash, final String payload) {
|
public static void verify(final String givenHash, final String payload) {
|
||||||
@ -22,8 +22,8 @@ public class LinuxEtcShadowHashGenerator {
|
|||||||
|
|
||||||
final var algorithm = HashGenerator.Algorithm.byPrefix(parts[1]);
|
final var algorithm = HashGenerator.Algorithm.byPrefix(parts[1]);
|
||||||
final var salt = parts.length == 4 ? parts[2] : parts[2] + "$" + parts[3];
|
final var salt = parts.length == 4 ? parts[2] : parts[2] + "$" + parts[3];
|
||||||
final var calcualatedHash = HashGenerator.using(algorithm).withSalt(salt).hash(payload);
|
final var calculatedHash = HashGenerator.using(algorithm).withSalt(salt).hash(payload);
|
||||||
if (!calcualatedHash.equals(givenHash)) {
|
if (!calculatedHash.equals(givenHash)) {
|
||||||
throw new IllegalArgumentException("invalid password");
|
throw new IllegalArgumentException("invalid password");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import java.nio.charset.Charset;
|
|||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LINUX_SHA512;
|
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LINUX_SHA512;
|
||||||
|
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LINUX_YESCRYPT;
|
||||||
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.MYSQL_NATIVE;
|
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.MYSQL_NATIVE;
|
||||||
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.SCRAM_SHA256;
|
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.SCRAM_SHA256;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@ -57,6 +58,18 @@ class HashGeneratorUnitTest {
|
|||||||
assertThat(throwable).hasMessage("invalid password");
|
assertThat(throwable).hasMessage("invalid password");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generatesLinuxSha512PasswordHash() {
|
||||||
|
final var hash = HashGenerator.using(LINUX_SHA512).withSalt("ooei1HK6JXVaI7KC").hash(GIVEN_PASSWORD);
|
||||||
|
assertThat(hash).isEqualTo(GIVEN_LINUX_GENERATED_SHA512_HASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generatesLinuxYescriptPasswordHash() {
|
||||||
|
final var hash = HashGenerator.using(LINUX_YESCRYPT).withSalt("wgYACPmBXvlMg2MzeZA0p1").hash(GIVEN_PASSWORD);
|
||||||
|
assertThat(hash).isEqualTo(GIVEN_LINUX_GENERATED_YESCRYPT_HASH);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void generatesMySqlNativePasswordHash() {
|
void generatesMySqlNativePasswordHash() {
|
||||||
final var hash = HashGenerator.using(MYSQL_NATIVE).hash("Test1234");
|
final var hash = HashGenerator.using(MYSQL_NATIVE).hash("Test1234");
|
||||||
|
Loading…
Reference in New Issue
Block a user