-
I've written a plugin for a password encryption scheme that FusionAuth doesn't currently support. Where can I share that?
-
Please go ahead and add it to this topic.
-
There's a skeleton github repo.
-
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=="}, }; } }