using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace MiniWiki.Web.Auth;
public class PasswordService
{
private const int SaltSize = 16;
private const int HashSize = 32;
private const int Iterations = 600_000; // OWASP recommendation
///
/// Hashes a password using PBKDF2 with a random salt
///
/// The password to hash
/// The hashed password
/// Thrown if is empty
public string HashPassword(string password)
{
ArgumentException.ThrowIfNullOrWhiteSpace(password);
var salt = RandomNumberGenerator.GetBytes(SaltSize);
var hash = KeyDerivation.Pbkdf2(
password,
salt,
KeyDerivationPrf.HMACSHA256,
Iterations,
HashSize);
var combined = new byte[SaltSize + HashSize];
Buffer.BlockCopy(salt, 0, combined, 0, SaltSize);
Buffer.BlockCopy(hash, 0, combined, SaltSize, HashSize);
return Convert.ToBase64String(combined);
}
///
/// Verifies if the given hash is equal to the
///
/// The password to compare
/// The hashed password to compare
/// True if the passwords match
public bool VerifyPassword(string password, string hashedPassword)
{
if (string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(hashedPassword)) return false;
try
{
var combined = Convert.FromBase64String(hashedPassword);
if (combined.Length != SaltSize + HashSize) return false;
// extract salt
var salt = new byte[SaltSize];
Buffer.BlockCopy(combined, 0, salt, 0, SaltSize);
// extract hash
var storedHash = new byte[HashSize];
Buffer.BlockCopy(combined, SaltSize, storedHash, 0, HashSize);
// hash the provided password with the extracted salt
var computedHash = KeyDerivation.Pbkdf2(
password,
salt,
KeyDerivationPrf.HMACSHA256,
Iterations,
HashSize);
// compare hashes
return CryptographicOperations.FixedTimeEquals(storedHash, computedHash);
}
catch
{
return false;
}
}
}