using System.Security.Cryptography; namespace FozFc.Api.Infrastructure.Security; /// /// PBKDF2-HMAC-SHA256 password hashing. Stored format: /// pbkdf2$<iterations>$<saltBase64>$<hashBase64>. /// public sealed class Pbkdf2PasswordHasher : IPasswordHasher { private const int Iterations = 100_000; private const int SaltSize = 16; private const int KeySize = 32; public string Hash(string password) { var salt = RandomNumberGenerator.GetBytes(SaltSize); var hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, HashAlgorithmName.SHA256, KeySize); return $"pbkdf2${Iterations}${Convert.ToBase64String(salt)}${Convert.ToBase64String(hash)}"; } public bool Verify(string password, string stored) { var parts = stored.Split('$'); if (parts.Length != 4 || parts[0] != "pbkdf2") return false; if (!int.TryParse(parts[1], out var iterations)) return false; byte[] salt, expected; try { salt = Convert.FromBase64String(parts[2]); expected = Convert.FromBase64String(parts[3]); } catch (FormatException) { return false; } var actual = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, expected.Length); return CryptographicOperations.FixedTimeEquals(actual, expected); } }