using Microsoft.Extensions.Options; using System.Security.Cryptography; namespace PrivaPub.StaticServices { public sealed class PasswordHasher : IPasswordHasher { private const int SaltSize = 16; private const int KeySize = 32; private HashingOptions Options { get; } public PasswordHasher(IOptionsMonitor options) { Options = options.CurrentValue; } public (bool verified, bool needsUpgrade) Check(string hash, string password) { var parts = hash.Split('~', 3); if (parts.Length != 3) throw new FormatException("Unexpected hash format."); var iterations = Convert.ToInt32(parts[2]); var salt = Convert.FromBase64String(parts[1]); var key = Convert.FromBase64String(parts[0]); var needsUpgrade = iterations != Options.Iterations; using (var algorithm = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA512)) { var keyToCheck = algorithm.GetBytes(KeySize); var verified = keyToCheck.SequenceEqual(key); return (verified, needsUpgrade); } } public string Hash(string password) { using (var algorithm = new Rfc2898DeriveBytes(password, SaltSize, Options.Iterations, HashAlgorithmName.SHA512)) { var key = Convert.ToBase64String(algorithm.GetBytes(KeySize)); var salt = Convert.ToBase64String(algorithm.Salt); return $"{key}~{salt}~{Options.Iterations}"; } } } public sealed class HashingOptions { public int Iterations { get; set; } = 9789; } }