using System.Security.Cryptography; using System.Text; namespace Backend.Security; public static class DemoCredentialHasher { private const int SaltSize = 16; private const int HashSize = 32; private const int Iterations = 100_000; public static (string Hash, string Salt) HashPassword(string password) { var salt = RandomNumberGenerator.GetBytes(SaltSize); var hash = Rfc2898DeriveBytes.Pbkdf2( password, salt, Iterations, HashAlgorithmName.SHA256, HashSize); return (Convert.ToBase64String(hash), Convert.ToBase64String(salt)); } public static bool VerifyPassword(string password, string expectedHash, string salt) { if (string.IsNullOrWhiteSpace(expectedHash) || string.IsNullOrWhiteSpace(salt)) { return false; } try { var saltBytes = Convert.FromBase64String(salt); var expectedBytes = Convert.FromBase64String(expectedHash); var candidateBytes = Rfc2898DeriveBytes.Pbkdf2( password, saltBytes, Iterations, HashAlgorithmName.SHA256, expectedBytes.Length); return CryptographicOperations.FixedTimeEquals(candidateBytes, expectedBytes); } catch (FormatException) { return false; } } public static bool FixedTimePlainTextEquals(string candidate, string expected) { var candidateBytes = Encoding.UTF8.GetBytes(candidate); var expectedBytes = Encoding.UTF8.GetBytes(expected); return candidateBytes.Length == expectedBytes.Length && CryptographicOperations.FixedTimeEquals(candidateBytes, expectedBytes); } }