femto-backend/Femto.Modules.Auth/Models/UserPassword.cs
2025-05-14 23:53:00 +02:00

73 lines
No EOL
1.8 KiB
C#

using System.Text;
using Geralt;
using JetBrains.Annotations;
namespace Femto.Modules.Auth.Models;
internal class UserPassword
{
private const int Iterations = 3;
private const int MemorySize = 67108864;
public Guid Id { get; private set; }
private byte[] Hash { get; set; }
private byte[] Salt { get; set; }
[UsedImplicitly]
private UserPassword() {}
public UserPassword(string password)
{
this.Id = Guid.NewGuid();
this.Salt = ComputeSalt();
this.Hash = ComputePasswordHash(password, Salt);
}
public bool Check(string password)
{
var matches = Argon2id.VerifyHash(
Hash,
Combine(password, Salt)
);
if (!matches)
return false;
if (Argon2id.NeedsRehash(Hash, Iterations, MemorySize))
{
this.Salt = ComputeSalt();
this.Hash = ComputePasswordHash(password, Salt);
}
return true;
}
private static byte[] ComputeSalt() => System.Security.Cryptography.RandomNumberGenerator.GetBytes(32);
private static byte[] ComputePasswordHash(string password, byte[] salt)
{
var hash = new byte[128];
try
{
Argon2id.ComputeHash(hash, Combine(password, salt), Iterations, MemorySize);
}
catch (Exception e)
{
throw new SetPasswordError("Failed to hash password", e);
}
return hash;
}
private static byte[] Combine(string password, byte[] salt)
{
var passwordBytes = Encoding.UTF8.GetBytes(password);
var hashInput = new byte[passwordBytes.Length + salt.Length];
passwordBytes.CopyTo(hashInput, 0);
salt.CopyTo(hashInput, passwordBytes.Length);
return hashInput;
}
}