using System.Text; using Geralt; using JetBrains.Annotations; namespace Femto.Modules.Auth.Models; internal class Password { private const int Iterations = 3; private const int MemorySize = 67108864; public byte[] Hash { get; private set; } public byte[] Salt { get; private set; } [UsedImplicitly] private Password() {} public Password(string password) { 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; } }