fix salt problem for yescrypt hashes in HashGenerator (#96)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #96
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-09-10 10:31:49 +02:00
parent fbd17a21e2
commit 8e02610679
3 changed files with 45 additions and 7 deletions

View File

@ -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<HashGenerator, String, String> implementation;
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.prefix = prefix;
this.optionalParam = optionalParam;
}
Algorithm(BiFunction<HashGenerator, String, String> 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")
);
}
}

View File

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

View File

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