use JNA + C-library-function crypt to create hash

This commit is contained in:
Michael Hoennig 2024-07-01 14:40:53 +02:00
parent 0e9db7e67a
commit 42e9417382
3 changed files with 33 additions and 11 deletions

View File

@ -66,7 +66,7 @@ dependencies {
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
implementation 'org.apache.commons:commons-text:1.11.0'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.76'
implementation 'net.java.dev.jna:jna:5.8.0'
implementation 'org.modelmapper:modelmapper:3.2.0'
implementation 'org.iban4j:iban4j:3.2.7-RELEASE'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'

View File

@ -6,8 +6,8 @@ import java.util.PriorityQueue;
import java.util.Queue;
import java.util.random.RandomGenerator;
import org.bouncycastle.crypto.generators.OpenBSDBCrypt;
import com.sun.jna.Library;
import com.sun.jna.Native;
public class LinuxEtcShadowHashGenerator {
@ -15,13 +15,13 @@ public class LinuxEtcShadowHashGenerator {
private static final Queue<String> predefinedSalts = new PriorityQueue<>();
public static final int SALT_LENGTH = 16;
public static final int COST_FACTOR = 13;
private final String plaintextPassword;
private Algorithm algorithm;
public enum Algorithm {
SHA512("6");
SHA512("6"),
YESCRYPT("y");
final String prefix;
@ -57,12 +57,12 @@ public class LinuxEtcShadowHashGenerator {
void verify(final String givenHash) {
final var parts = givenHash.split("\\$");
if (parts.length != 4) {
throw new IllegalArgumentException("not a "+algorithm.name()+" Linux hash: " + givenHash);
if (parts.length < 3 || parts.length > 5) {
throw new IllegalArgumentException("not a " + algorithm.name() + " Linux hash: " + givenHash);
}
algorithm = Algorithm.byPrefix(parts[1]);
salt = parts[2];
salt = parts.length == 4 ? parts[2] : parts[2] + "$" + parts[3];
if (!generate().equals(givenHash)) {
throw new IllegalArgumentException("invalid password");
@ -76,9 +76,8 @@ public class LinuxEtcShadowHashGenerator {
if (plaintextPassword == null) {
throw new IllegalStateException("no password given");
}
final var hash = OpenBSDBCrypt.generate(plaintextPassword.toCharArray(), salt.getBytes(), COST_FACTOR);
final var hashedPassword = new String(org.bouncycastle.util.encoders.Base64.encode(hash.getBytes()));
return "$" + algorithm.prefix + "$" + salt + "$" + hashedPassword;
return NativeCryptLibrary.INSTANCE.crypt(plaintextPassword, "$" + algorithm.prefix + "$" + salt);
}
public static void nextSalt(final String salt) {
@ -101,4 +100,13 @@ public class LinuxEtcShadowHashGenerator {
}
return withSalt(stringBuilder.toString());
}
public static void main(String[] args) {
System.out.println(NativeCryptLibrary.INSTANCE.crypt("given password", "$6$abcdefghijklmno"));
}
public interface NativeCryptLibrary extends Library {
NativeCryptLibrary INSTANCE = Native.load("crypt", NativeCryptLibrary.class);
String crypt(String password, String salt);
}
}

View File

@ -13,6 +13,20 @@ class LinuxEtcShadowHashGeneratorUnitTest {
final String WRONG_PASSWORD = "wrong password";
final String GIVEN_SALT = "0123456789abcdef";
// generated via mkpasswd for plaintext password GIVEN_PASSWORD (see above)
final String GIVEN_SHA512_HASH = "$6$ooei1HK6JXVaI7KC$sY5d9fEOr36hjh4CYwIKLMfRKL1539bEmbVCZ.zPiH0sv7jJVnoIXb5YEefEtoSM2WWgDi9hr7vXRe3Nw8zJP/";
final String GIVEN_YESCRYPT_HASH = "$y$j9T$wgYACPmBXvlMg2MzeZA0p1$KXUzd28nG.67GhPnBZ3aZsNNA5bWFdL/dyG4wS0iRw7";
@Test
void verifiesPasswordAgainstSha512HashFromMkpasswd() {
hash(GIVEN_PASSWORD).verify(GIVEN_SHA512_HASH); // throws exception if wrong
}
@Test
void verifiesPasswordAgainstYescryptHashFromMkpasswd() {
hash(GIVEN_PASSWORD).verify(GIVEN_YESCRYPT_HASH); // throws exception if wrong
}
@Test
void verifiesHashedPasswordWithRandomSalt() {
final var hash = hash(GIVEN_PASSWORD).using(SHA512).withRandomSalt().generate();