using System.Security.Cryptography; using Tiku.Application.Auth; namespace Tiku.Infrastructure.Auth; public sealed class PasswordHasher : IPasswordHasher { private const int SaltSize = 16; private const int HashSize = 32; private const int Iterations = 210_000; private const string Prefix = "pbkdf2-sha256"; public string Hash(string password) { ArgumentException.ThrowIfNullOrWhiteSpace(password); var salt = RandomNumberGenerator.GetBytes(SaltSize); var hash = Rfc2898DeriveBytes.Pbkdf2( password, salt, Iterations, HashAlgorithmName.SHA256, HashSize); return string.Join( '$', Prefix, Iterations.ToString(System.Globalization.CultureInfo.InvariantCulture), Convert.ToBase64String(salt), Convert.ToBase64String(hash)); } public bool Verify(string password, string passwordHash) { if (string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(passwordHash)) { return false; } var parts = passwordHash.Split('$'); if (parts.Length != 4 || !string.Equals(parts[0], Prefix, StringComparison.Ordinal) || !int.TryParse(parts[1], out var iterations)) { return false; } var salt = Convert.FromBase64String(parts[2]); var expected = Convert.FromBase64String(parts[3]); var actual = Rfc2898DeriveBytes.Pbkdf2( password, salt, iterations, HashAlgorithmName.SHA256, expected.Length); return CryptographicOperations.FixedTimeEquals(actual, expected); } }