56 lines
1.8 KiB
C#
56 lines
1.8 KiB
C#
using Konscious.Security.Cryptography;
|
|
using System;
|
|
using System.Text;
|
|
|
|
namespace Mirea.Api.Security.Services;
|
|
|
|
public class PasswordHashService
|
|
{
|
|
public int SaltSize { private get; init; }
|
|
public int HashSize { private get; init; }
|
|
public int Iterations { private get; init; }
|
|
public int Memory { private get; init; }
|
|
public int Parallelism { private get; init; }
|
|
public string? Secret { private get; init; }
|
|
|
|
private ReadOnlySpan<byte> HashPassword(string password, ReadOnlySpan<byte> salt)
|
|
{
|
|
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
|
|
{
|
|
Iterations = Iterations,
|
|
MemorySize = Memory,
|
|
DegreeOfParallelism = Parallelism,
|
|
Salt = salt.ToArray()
|
|
};
|
|
|
|
if (!string.IsNullOrEmpty(Secret))
|
|
argon2.KnownSecret = Convert.FromBase64String(Secret);
|
|
|
|
return argon2.GetBytes(HashSize);
|
|
}
|
|
|
|
private static bool ConstantTimeComparison(ReadOnlySpan<byte> a, ReadOnlySpan<byte> b)
|
|
{
|
|
if (a.Length != b.Length)
|
|
return false;
|
|
|
|
int result = 0;
|
|
for (int i = 0; i < a.Length; i++)
|
|
result |= a[i] ^ b[i];
|
|
return result == 0;
|
|
}
|
|
|
|
public (string Salt, string Hash) HashPassword(string password)
|
|
{
|
|
var salt = GeneratorKey.GenerateBytes(SaltSize);
|
|
var hash = HashPassword(password, salt);
|
|
|
|
return (Convert.ToBase64String(salt), Convert.ToBase64String(hash));
|
|
}
|
|
|
|
public bool VerifyPassword(string password, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> hash) =>
|
|
ConstantTimeComparison(HashPassword(password, salt), hash);
|
|
|
|
public bool VerifyPassword(string password, string saltBase64, string hashBase64) =>
|
|
VerifyPassword(password, Convert.FromBase64String(saltBase64), Convert.FromBase64String(hashBase64));
|
|
} |