using System;
using System.Security.Cryptography;
using System.Text;
using Konscious.Security.Cryptography;
namespace PasswordHasher
{
///
/// Provides secure password hashing functionality using Argon2id algorithm
///
public class PasswordHasher
{
///
/// Size of the salt in bytes
///
private const int SaltSize = 16;
///
/// Size of the hash in bytes
///
private const int HashSize = 32;
///
/// Number of threads to use for parallel computation
///
private const int DegreeOfParallelism = 1;
///
/// Number of iterations for the Argon2id algorithm
///
private const int Iterations = 2;
///
/// Memory size in KB to use
///
private const int MemorySize = 19456; // 19 MB
///
/// Generates a secure hash of a password using Argon2id with a random salt
///
/// The plain text password to hash
/// A Base64 string containing the combined salt and hash
/// Thrown when password is null
public string HashPassword(string password)
{
// Generate a random salt
byte[] salt = new byte[SaltSize];
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
// Create hash
byte[] hash = HashPassword(password, salt);
// Combine salt and hash
var combinedBytes = new byte[salt.Length + hash.Length];
Array.Copy(salt, 0, combinedBytes, 0, salt.Length);
Array.Copy(hash, 0, combinedBytes, salt.Length, hash.Length);
// Convert to base64 for storage
return Convert.ToBase64String(combinedBytes);
}
///
/// Generates a password hash using Argon2id with a specific salt
///
/// The plain text password
/// The salt to use for hashing
/// A byte array containing the password hash
private byte[] HashPassword(string password, byte[] salt)
{
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
{
Salt = salt,
DegreeOfParallelism = DegreeOfParallelism,
Iterations = Iterations,
MemorySize = MemorySize
};
return argon2.GetBytes(HashSize);
}
///
/// Verifies if a password matches a stored hash
///
/// The plain text password to verify
/// The stored hash in Base64 format
/// True if the password matches the hash, false otherwise
/// Thrown when password or hashedPassword are null
/// Thrown when hashedPassword is not in valid Base64 format
public bool VerifyPassword(string password, string hashedPassword)
{
// Decode the stored hash
byte[] combinedBytes = Convert.FromBase64String(hashedPassword);
// Extract salt and hash
byte[] salt = new byte[SaltSize];
byte[] hash = new byte[HashSize];
Array.Copy(combinedBytes, 0, salt, 0, SaltSize);
Array.Copy(combinedBytes, SaltSize, hash, 0, HashSize);
// Compute hash for the input password
byte[] newHash = HashPassword(password, salt);
// Compare the hashes
return CryptographicOperations.FixedTimeEquals(hash, newHash);
}
}
}