feat: implement password hasher
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FiscalOS.API.Identity;
|
||||
|
||||
internal interface IPasswordHasher
|
||||
{
|
||||
string Hash(string password);
|
||||
bool Verify(string providedPassword, string hashedPassword);
|
||||
}
|
||||
|
||||
internal class PasswordHasher : IPasswordHasher
|
||||
{
|
||||
private const int SaltSize = 16;
|
||||
private const int HashSize = 32;
|
||||
private const int Iterations = 100_000;
|
||||
|
||||
private static readonly HashAlgorithmName HashAlgorithm = HashAlgorithmName.SHA512;
|
||||
|
||||
private PasswordHasher()
|
||||
{
|
||||
}
|
||||
|
||||
public static PasswordHasher New()
|
||||
{
|
||||
return new();
|
||||
}
|
||||
|
||||
public string Hash(string password)
|
||||
{
|
||||
var salt = RandomNumberGenerator.GetBytes(SaltSize);
|
||||
var hash = Rfc2898DeriveBytes.Pbkdf2(
|
||||
password,
|
||||
salt,
|
||||
Iterations,
|
||||
HashAlgorithm,
|
||||
HashSize
|
||||
);
|
||||
|
||||
var hashBytes = new byte[SaltSize + HashSize];
|
||||
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
|
||||
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
|
||||
|
||||
return Convert.ToBase64String(hashBytes);
|
||||
}
|
||||
|
||||
public bool Verify(string providedPassword, string hashedPassword)
|
||||
{
|
||||
var hashBytes = Convert.FromBase64String(hashedPassword);
|
||||
|
||||
var salt = new byte[SaltSize];
|
||||
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
|
||||
|
||||
var storedHash = new byte[HashSize];
|
||||
Array.Copy(hashBytes, SaltSize, storedHash, 0, HashSize);
|
||||
|
||||
var computedHash = Rfc2898DeriveBytes.Pbkdf2(
|
||||
providedPassword,
|
||||
salt,
|
||||
Iterations,
|
||||
HashAlgorithm,
|
||||
HashSize
|
||||
);
|
||||
|
||||
return CryptographicOperations.FixedTimeEquals(storedHash, computedHash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using FiscalOS.API.Identity;
|
||||
|
||||
namespace FiscalOS.API.Tests.Unit;
|
||||
|
||||
public class PasswordHasherTests
|
||||
{
|
||||
private readonly PasswordHasher _sut = PasswordHasher.New();
|
||||
|
||||
[Fact]
|
||||
public void Hash_WhenGivenPassword_ShouldReturnHashedPassword()
|
||||
{
|
||||
var password = "SecurePassword123!";
|
||||
|
||||
var result = _sut.Hash(password);
|
||||
|
||||
result.Should().NotBeNullOrEmpty();
|
||||
result.Should().NotBe(password);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Verify_WhenGivenCorrectPassword_ShouldReturnTrue()
|
||||
{
|
||||
var password = "SecurePassword123!";
|
||||
var hashedPassword = _sut.Hash(password);
|
||||
|
||||
var result = _sut.Verify(password, hashedPassword);
|
||||
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Verify_WhenGivenIncorrectPassword_ShouldReturnFalse()
|
||||
{
|
||||
var password = "SecurePassword123!";
|
||||
var wrongPassword = "WrongPassword!";
|
||||
var hashedPassword = _sut.Hash(password);
|
||||
|
||||
var result = _sut.Verify(wrongPassword, hashedPassword);
|
||||
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user