add PostgreSQLScramSHA256 password encoding (working, but needs cleanup)
This commit is contained in:
parent
46fce275ae
commit
29994a851a
@ -31,7 +31,8 @@ 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"),
|
||||||
MYSQL_NATIVE(MySQLNativePasswordHashGenerator::hash, "*");
|
MYSQL_NATIVE(MySQLNativePasswordHashGenerator::hash, "*"),
|
||||||
|
SCRAM_SHA256(PostgreSQLScramSHA256::hash, "*");
|
||||||
|
|
||||||
final BiFunction<HashGenerator, String, String> implementation;
|
final BiFunction<HashGenerator, String, String> implementation;
|
||||||
final String prefix;
|
final String prefix;
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
package net.hostsharing.hsadminng.hash;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public class PostgreSQLScramSHA256 {
|
||||||
|
|
||||||
|
private static final String PBKDF_2_WITH_HMAC_SHA256 = "PBKDF2WithHmacSHA256";
|
||||||
|
private static final String HMAC_SHA256 = "HmacSHA256";
|
||||||
|
private static final String SHA256 = "SHA-256";
|
||||||
|
private static final int ITERATIONS = 4096;
|
||||||
|
public static final int KEY_LENGTH_IN_BITS = 256;
|
||||||
|
|
||||||
|
private static final PostgreSQLScramSHA256 scram = new PostgreSQLScramSHA256();
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static String hash(final HashGenerator generator, final String password) {
|
||||||
|
if (generator.getSalt() == null) {
|
||||||
|
throw new IllegalStateException("no salt given");
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] salt = generator.getSalt().getBytes(Charset.forName("latin1")); // Base64.getEncoder().encode(generator.getSalt().getBytes());
|
||||||
|
final byte[] saltedPassword = scram.generateSaltedPassword(password, salt);
|
||||||
|
final byte[] clientKey = scram.hmacSHA256(saltedPassword, "Client Key".getBytes());
|
||||||
|
final byte[] storedKey = MessageDigest.getInstance(SHA256).digest(clientKey);
|
||||||
|
final byte[] serverKey = scram.hmacSHA256(saltedPassword, "Server Key".getBytes());
|
||||||
|
|
||||||
|
return "SCRAM-SHA-256${iterations}:{base64EncodedSalt}${base64EncodedStoredKey}:{base64EncodedServerKey}"
|
||||||
|
.replace("{iterations}", Integer.toString(ITERATIONS))
|
||||||
|
.replace("{base64EncodedSalt}", base64(salt))
|
||||||
|
.replace("{base64EncodedStoredKey}", base64(storedKey))
|
||||||
|
.replace("{base64EncodedServerKey}", base64(serverKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String base64(final byte[] salt) {
|
||||||
|
return Base64.getEncoder().encodeToString(salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] generateSaltedPassword(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
|
final var spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH_IN_BITS);
|
||||||
|
return SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA256).generateSecret(spec).getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] hmacSHA256(byte[] key, byte[] message)
|
||||||
|
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||||
|
final var mac = Mac.getInstance(HMAC_SHA256);
|
||||||
|
mac.init(new SecretKeySpec(key, HMAC_SHA256));
|
||||||
|
return mac.doFinal(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,8 +2,12 @@ package net.hostsharing.hsadminng.hash;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
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_SHA512;
|
||||||
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 org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
|
||||||
@ -49,8 +53,36 @@ class HashGeneratorUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void verifiesMySqlNativePassword() {
|
void generatesMySqlNativePasswordHash() {
|
||||||
final var hash = HashGenerator.using(MYSQL_NATIVE).hash("Test1234");
|
final var hash = HashGenerator.using(MYSQL_NATIVE).hash("Test1234");
|
||||||
assertThat(hash).isEqualTo("*14F1A8C42F8B6D4662BB3ED290FD37BF135FE45C");
|
assertThat(hash).isEqualTo("*14F1A8C42F8B6D4662BB3ED290FD37BF135FE45C");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generatePostgreSqlScramPasswordHash() {
|
||||||
|
//final var salt = new String(Base64.getDecoder().decode("3gsrkV5e1VZweIFiKfvoeQ=="));
|
||||||
|
final var postgresBase64Salt = Base64.getDecoder().decode("3gsrkV5e1VZweIFiKfvoeQ==");
|
||||||
|
final var reEncodedSalt = Base64.getEncoder().encodeToString(postgresBase64Salt);
|
||||||
|
final var hash = HashGenerator.using(SCRAM_SHA256).withSalt(new String(postgresBase64Salt, Charset.forName("latin1"))).hash("Test1234");
|
||||||
|
|
||||||
|
assertThat(hash).isEqualTo(
|
||||||
|
"SCRAM-SHA-256$4096:3gsrkV5e1VZweIFiKfvoeQ==$/8I29AMTJ+7W9ceeKhc5LsfTrTHF6/+m/qstv2h0kpo=:+MDwFXgAjHHSnlqgU5adOPtW0qpbFUMrYp59Xs7ns0U=");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALTER USER your_username WITH PASSWORD 'SCRAM-SHA-256$iterations:base64-encoded-salt$base64-encoded-stored-key$base64-encoded-server-key';
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void xxx() {
|
||||||
|
String postgresqlBase64Salt = "3gsrkV5e1VZweIFiKfvoeQ=="; // Example Base64 encoded salt from PostgreSQL
|
||||||
|
|
||||||
|
// Decode the base64 salt using the standard Base64 decoder
|
||||||
|
byte[] decodedSalt = Base64.getDecoder().decode(postgresqlBase64Salt);
|
||||||
|
|
||||||
|
// Re-encode the salt using the standard Base64 encoder
|
||||||
|
String javaBase64Salt = Base64.getEncoder().encodeToString(decodedSalt);
|
||||||
|
|
||||||
|
// Print both the original and re-encoded salts
|
||||||
|
System.out.println("Orig PostgreSQL Base64 Salt: " + postgresqlBase64Salt);
|
||||||
|
System.out.println("Re-encoded Java Base64 Salt: " + javaBase64Salt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user