From 59402def7dfc3e70ee3fea942683bbc22c57d187 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 6 Sep 2024 15:16:26 +0200 Subject: [PATCH] fix salt problem for yescrypt hashes in HashGenerator --- .../hsadminng/hash/HashGenerator.java | 33 ++++++++++++++++--- .../hash/LinuxEtcShadowHashGenerator.java | 6 ++-- .../hsadminng/hash/HashGeneratorUnitTest.java | 13 ++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hash/HashGenerator.java b/src/main/java/net/hostsharing/hsadminng/hash/HashGenerator.java index 44f41281..cd16b697 100644 --- a/src/main/java/net/hostsharing/hsadminng/hash/HashGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/hash/HashGenerator.java @@ -31,22 +31,37 @@ public final class HashGenerator { public enum Algorithm { 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, "*"), SCRAM_SHA256(PostgreSQLScramSHA256::hash, "SCRAM-SHA-256"); final BiFunction implementation; final String prefix; + final String optionalParam; - Algorithm(BiFunction implementation, final String prefix) { + Algorithm(BiFunction implementation, final String prefix, final String optionalParam) { this.implementation = implementation; this.prefix = prefix; + this.optionalParam = optionalParam; + } + + Algorithm(BiFunction implementation, final String prefix) { + this(implementation, prefix, null); } static Algorithm byPrefix(final String prefix) { return Arrays.stream(Algorithm.values()).filter(a -> a.prefix.equals(prefix)).findAny() .orElseThrow(() -> new IllegalArgumentException("unknown hash algorithm: '" + prefix + "'")); } + + String enrichedSalt(final String salt) { + return prefix + "$" + salt; + } } private final Algorithm algorithm; @@ -60,7 +75,7 @@ public final class HashGenerator { this.algorithm = algorithm; } - public static void enableChouldBeHash(final boolean enable) { + public static void enableCouldBeHash(final boolean enable) { couldBeHashEnabled = enable; } @@ -73,7 +88,11 @@ public final class HashGenerator { 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) { @@ -102,4 +121,10 @@ public final class HashGenerator { } 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") + ); + } } diff --git a/src/main/java/net/hostsharing/hsadminng/hash/LinuxEtcShadowHashGenerator.java b/src/main/java/net/hostsharing/hsadminng/hash/LinuxEtcShadowHashGenerator.java index aaed6fd0..b5aa58b4 100644 --- a/src/main/java/net/hostsharing/hsadminng/hash/LinuxEtcShadowHashGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/hash/LinuxEtcShadowHashGenerator.java @@ -10,7 +10,7 @@ public class LinuxEtcShadowHashGenerator { 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) { @@ -22,8 +22,8 @@ public class LinuxEtcShadowHashGenerator { final var algorithm = HashGenerator.Algorithm.byPrefix(parts[1]); final var salt = parts.length == 4 ? parts[2] : parts[2] + "$" + parts[3]; - final var calcualatedHash = HashGenerator.using(algorithm).withSalt(salt).hash(payload); - if (!calcualatedHash.equals(givenHash)) { + final var calculatedHash = HashGenerator.using(algorithm).withSalt(salt).hash(payload); + if (!calculatedHash.equals(givenHash)) { throw new IllegalArgumentException("invalid password"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hash/HashGeneratorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hash/HashGeneratorUnitTest.java index aa5b6369..fcb2ce3d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hash/HashGeneratorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hash/HashGeneratorUnitTest.java @@ -6,6 +6,7 @@ import java.nio.charset.Charset; import java.util.Base64; 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.SCRAM_SHA256; import static org.assertj.core.api.Assertions.assertThat; @@ -57,6 +58,18 @@ class HashGeneratorUnitTest { 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 void generatesMySqlNativePasswordHash() { final var hash = HashGenerator.using(MYSQL_NATIVE).hash("Test1234");