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