using System.Security.Cryptography; namespace DXPBLL.Auth; // PBKDF2 (.NET built-in Rfc2898DeriveBytes) — no external dependency needed. Format: // "{iterations}.{saltBase64}.{hashBase64}", so the iteration count can be raised later without // invalidating already-hashed passwords (they just keep verifying against their own stored count). public static class DXPPasswordHasher { private const int SaltSize = 16; private const int HashSize = 32; private const int Iterations = 210_000; // OWASP-recommended minimum for PBKDF2-SHA256 (2023+) public static string Hash(string password) { var salt = RandomNumberGenerator.GetBytes(SaltSize); var hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, HashAlgorithmName.SHA256, HashSize); return $"{Iterations}.{Convert.ToBase64String(salt)}.{Convert.ToBase64String(hash)}"; } public static bool Verify(string password, string storedHash) { var parts = storedHash.Split('.', 3); if (parts.Length != 3) return false; var iterations = int.Parse(parts[0]); var salt = Convert.FromBase64String(parts[1]); var expected = Convert.FromBase64String(parts[2]); var actual = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, expected.Length); return CryptographicOperations.FixedTimeEquals(actual, expected); } }