fix salt problem for yescrypt hashes in HashGenerator #96

Merged
hsh-michaelhoennig merged 1 commits from fix-yescrypt-hash into master 2024-09-10 10:31:50 +02:00
3 changed files with 45 additions and 7 deletions

View File

@ -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")
);
}
} }

View File

@ -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");
} }
} }

View File

@ -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");