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=="},
};
}
}