public sealed class PasswordHasher : IPasswordHasher
{
private const int SaltSize = 16;
private const int HashSize = 32;
private const int Iterations = 100000;
private static readonly HashAlgorithmName Algorithm = HashAlgorithmName.SHA512;
public string Hash(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, Algorithm, HashSize);
return $"{Convert.ToHexString(hash)}-{Convert.ToHexString(salt)}";
}
public bool Verify(string password, string passwordHash)
{
if (string.IsNullOrWhiteSpace(password))
throw new ArgumentException("Password cannot be null or empty.", nameof(password));
if (string.IsNullOrWhiteSpace(passwordHash))
throw new ArgumentException("Stored password hash cannot be null or empty.", nameof(passwordHash));
string[] parts = passwordHash.Split('-');
if (parts.Length != 2)
throw new FormatException("Invalid password hash format.");
byte[] hash;
byte[] salt;
try
{
hash = Convert.FromHexString(parts[0]);
salt = Convert.FromHexString(parts[1]);
}
catch (FormatException)
{
throw new FormatException("Password hash or salt is not a valid hexadecimal string.");
}
byte[] inputHash = Rfc2898DeriveBytes.Pbkdf2(
password,
salt,
Iterations,
Algorithm,
HashSize
);
bool matches = CryptographicOperations.FixedTimeEquals(hash, inputHash);
return matches;
}
}