55 lines
1.9 KiB
C#
55 lines
1.9 KiB
C#
using System.Security.Cryptography;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace lxc_songlist.Services;
|
|
|
|
public sealed class AdminPasswordService(IOptions<AdminAuthOptions> options)
|
|
{
|
|
private const int SaltBytes = 16;
|
|
private const int HashBytes = 32;
|
|
private const int Iterations = 210_000;
|
|
|
|
public bool Verify(string password)
|
|
{
|
|
var configuredHash = options.Value.PasswordHash;
|
|
if (!string.IsNullOrWhiteSpace(configuredHash))
|
|
{
|
|
return VerifyHash(password, configuredHash);
|
|
}
|
|
|
|
var configuredPassword = options.Value.Password;
|
|
return !string.IsNullOrEmpty(configuredPassword)
|
|
&& CryptographicOperations.FixedTimeEquals(
|
|
System.Text.Encoding.UTF8.GetBytes(password),
|
|
System.Text.Encoding.UTF8.GetBytes(configuredPassword));
|
|
}
|
|
|
|
public static string CreateHash(string password)
|
|
{
|
|
var salt = RandomNumberGenerator.GetBytes(SaltBytes);
|
|
var hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, HashAlgorithmName.SHA256, HashBytes);
|
|
return $"pbkdf2${Iterations}${Convert.ToBase64String(salt)}${Convert.ToBase64String(hash)}";
|
|
}
|
|
|
|
private static bool VerifyHash(string password, string storedHash)
|
|
{
|
|
var parts = storedHash.Split('$');
|
|
if (parts.Length != 4 || parts[0] != "pbkdf2" || !int.TryParse(parts[1], out var iterations))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
var salt = Convert.FromBase64String(parts[2]);
|
|
var expectedHash = Convert.FromBase64String(parts[3]);
|
|
var actualHash = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, expectedHash.Length);
|
|
return CryptographicOperations.FixedTimeEquals(actualHash, expectedHash);
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|