In case it helps anyone, a version of the ASP.NET Core Identity PasswordHasher HashPasswordV3
package com.mycompany.fusionauth.plugins;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import io.fusionauth.plugin.spi.security.PasswordEncryptor;
/**
* Example password hashing based on Asp.Net Core Identity PasswordHasher HashPasswordV3.
*/
public class ExampleDotNetPBDKF2HMACSHA256PasswordEncryptor implements PasswordEncryptor {
  @Override
  public int defaultFactor() {
    return 10_000;
  }
  @Override
  public String encrypt(String password, String salt, int factor) {
    if (factor <= 0) {
      throw new IllegalArgumentException("Invalid factor value [" + factor + "]");
    }
    SecretKeyFactory keyFactory;
    try {
      keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalStateException("No such algorithm [PBKDF2WithHmacSHA256]");
    }
	int keyLength = 32; // numBytesRequested
	byte[] saltBytes = Base64.getDecoder().decode(salt); // assumes Base64 encoded salt. saltSize: 16 bytes
    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, factor, keyLength * 8);
    SecretKey secret;
    try {
      secret = keyFactory.generateSecret(keySpec); // subkey
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException("Could not generate secret key for algorithm [PBKDF2WithHmacSHA256]");
    }
	
	byte[] outputBytes = new byte[13 + saltBytes.length + secret.getEncoded().length];
	outputBytes[0] = 0x01; // format marker
	WriteNetworkByteOrder(outputBytes, 1, 1);
	WriteNetworkByteOrder(outputBytes, 5, factor);
	WriteNetworkByteOrder(outputBytes, 9, saltBytes.length);
	System.arraycopy(saltBytes, 0, outputBytes, 13, saltBytes.length);
	System.arraycopy(secret.getEncoded(), 0, outputBytes, 13 + saltBytes.length, secret.getEncoded().length);
	
	return new String(Base64.getEncoder().encode(outputBytes));
  }
  
  private static void WriteNetworkByteOrder(byte[] buffer, int offset, int value)
  {
	buffer[offset + 0] = (byte)(value >> 24);
	buffer[offset + 1] = (byte)(value >> 16);
	buffer[offset + 2] = (byte)(value >> 8);
	buffer[offset + 3] = (byte)(value >> 0);
  }
}
package com.mycompany.fusionauth.plugins;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
public class ExampleDotNetPBDKF2HMACSHA256PasswordEncryptorTest {
  @Test(dataProvider = "hashes")
  public void encrypt(String password, String salt, String hash) {
    ExampleDotNetPBDKF2HMACSHA256PasswordEncryptor encryptor = new ExampleDotNetPBDKF2HMACSHA256PasswordEncryptor();
    assertEquals(encryptor.encrypt(password, salt, 10_000), hash);
  }
  @DataProvider(name = "hashes")
  public Object[][] hashes() {
    return new Object[][]{
        {"MyExamplePassword", "CVsv6SwPJr7WDrVvAb+7aw==", "AQAAAAEAACcQAAAAEAlbL+ksDya+1g61bwG/u2ssOcnQU6Q2xo9tmijJv0zM2GsxeOl04NSpXRsAveBBag=="},
    };
  }
}