using System.Security.Cryptography; using System.Text; using Konscious.Security.Cryptography; namespace SimpleWiki.Services; public static class PasswordHasher { private const int Iterations = 3; private const int MemorySizeKb = 65536; private const int Parallelism = 1; private const int SaltLength = 16; private const int HashLength = 32; public static string HashPassword(string password) { if (string.IsNullOrWhiteSpace(password)) { throw new ArgumentException("Пароль пустой."); } var salt = RandomNumberGenerator.GetBytes(SaltLength); using var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)) { Salt = salt, Iterations = Iterations, DegreeOfParallelism = Parallelism, MemorySize = MemorySizeKb }; var hash = argon2.GetBytes(HashLength); var combined = new byte[SaltLength + HashLength]; Buffer.BlockCopy(salt, 0, combined, 0, SaltLength); Buffer.BlockCopy(hash, 0, combined, SaltLength, HashLength); return Convert.ToBase64String(combined); } public static bool VerifyPassword(string password, string hashPassword) { if (string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(hashPassword)) { return false; } byte[] combined; try { combined = Convert.FromBase64String(hashPassword); } catch { return false; } if (combined.Length < SaltLength + HashLength) { return false; } var salt = combined.Take(SaltLength).ToArray(); var originalHash = combined.Skip(SaltLength).Take(HashLength).ToArray(); using var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)) { Salt = salt, Iterations = Iterations, DegreeOfParallelism = Parallelism, MemorySize = MemorySizeKb }; var newHash = argon2.GetBytes(HashLength); return CryptographicOperations.FixedTimeEquals(originalHash, newHash); } }