MireaBackend/Security/Services/PasswordHashService.cs

77 lines
2.6 KiB
C#

using Konscious.Security.Cryptography;
using System;
using System.Buffers.Text;
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 static ReadOnlySpan<byte> GenerateRandomKeyBytes(int size)
{
var key = new byte[size];
using var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
rng.GetNonZeroBytes(key);
return key;
}
public static string GenerateRandomKeyStringBase64(int size) =>
Convert.ToBase64String(GenerateRandomKeyBytes(size));
public static string GenerateRandomKeyString(int size)
{
var randomBytes = GenerateRandomKeyBytes(size);
Span<byte> utf8Bytes = new byte[Base64.GetMaxEncodedToUtf8Length(randomBytes.Length)];
Base64.EncodeToUtf8(randomBytes, utf8Bytes, out _, out _);
return Encoding.UTF8.GetString(utf8Bytes);
}
public (string Salt, string Hash) HashPassword(string password)
{
var salt = GenerateRandomKeyBytes(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));
}