using Isopoh.Cryptography.Argon2; using Isopoh.Cryptography.SecureArray; using System.Security.Cryptography; using System.Text; namespace Hasher { /// /// A utility class for generating and verifying Argon2 hashes. /// public static class Argon2dHasher { private static readonly RandomNumberGenerator Rng = RandomNumberGenerator.Create(); /// /// Generates an Argon2 hash for the given password and salt. /// /// The password to hash. /// A random salt to use for the hash. If null, a new 16-byte salt will be generated. /// The generated Argon2 hash as a string. public static string GenerateArgon2Hash(string password, byte[]? salt) { if (salt == null) { salt = new byte[16]; } byte[] pwBytes = Encoding.UTF8.GetBytes(password); // Generate a random salt Rng.GetBytes(salt); var config = new Argon2Config { Type = Argon2Type.DataIndependentAddressing, Version = Argon2Version.Nineteen, TimeCost = 6, Lanes = 3, Threads = Environment.ProcessorCount - 1, // higher than "Lanes" doesn't help (or hurt) Password = pwBytes, Salt = salt, }; var argon2 = new Argon2(config); string hashString; using(SecureArray hashA = argon2.Hash()) { hashString = config.EncodeString(hashA.Buffer); } return hashString; } /// /// Verifies that a given password matches an Argon2 hash. /// /// The password to check. /// The Argon2 hash to check against. /// True if the password matches the hash, false otherwise. public static bool VerifyArgon2Hash(string password, string hash) { byte[] pwBytes = Encoding.UTF8.GetBytes(password); var valid = Argon2.Verify(hash, pwBytes); return valid; } } }