This password hasher is the same used by ASP.NET Identity.
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using JWTAPI.Core.Security.Hashing;
namespace JWTAPI.Security.Hashing;
public interface IPasswordHasher
{
string HashPassword(string password);
bool PasswordMatches(string providedPassword, string passwordHash);
}
/// <summary>
/// This password hasher is the same used by ASP.NET Identity.
/// Explanation: https://stackoverflow.com/questions/20621950/asp-net-identity-default-password-hasher-how-does-it-work-and-is-it-secure
/// Full implementation: https://gist.github.com/malkafly/e873228cb9515010bdbe
/// </summary>
public class PasswordHasher : IPasswordHasher
{
public string HashPassword(string password)
{
byte[] salt;
byte[] buffer2;
if (string.IsNullOrEmpty(password))
{
throw new ArgumentNullException("password");
}
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, 0x10, 0x3e8))
{
salt = bytes.Salt;
buffer2 = bytes.GetBytes(0x20);
}
byte[] dst = new byte[0x31];
Buffer.BlockCopy(salt, 0, dst, 1, 0x10);
Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20);
return Convert.ToBase64String(dst);
}
public bool PasswordMatches(string providedPassword, string passwordHash)
{
byte[] buffer4;
if (passwordHash == null)
{
return false;
}
if (providedPassword == null)
{
throw new ArgumentNullException("providedPassword");
}
byte[] src = Convert.FromBase64String(passwordHash);
if ((src.Length != 0x31) || (src[0] != 0))
{
return false;
}
byte[] dst = new byte[0x10];
Buffer.BlockCopy(src, 1, dst, 0, 0x10);
byte[] buffer3 = new byte[0x20];
Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(providedPassword, dst, 0x3e8))
{
buffer4 = bytes.GetBytes(0x20);
}
return ByteArraysEqual(buffer3, buffer4);
}
[MethodImpl(MethodImplOptions.NoOptimization)]
private bool ByteArraysEqual(byte[] a, byte[] b)
{
if (ReferenceEquals(a, b))
{
return true;
}
if (a == null || b == null || a.Length != b.Length)
{
return false;
}
bool areSame = true;
for (int i = 0; i < a.Length; i++)
{
areSame &= (a[i] == b[i]);
}
return areSame;
}
}
// --------------------------------------
// Source: https://github.com/m-jovanovic/event-reminder/blob/main/EventReminder.Infrastructure/Cryptography/PasswordHasher.cs
using System;
using System.Security.Cryptography;
using EventReminder.Application.Core.Abstractions.Cryptography;
using EventReminder.Domain.Services;
using EventReminder.Domain.ValueObjects;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace EventReminder.Infrastructure.Cryptography
{
/// <summary>
/// Represents the password hasher, used for hashing passwords and verifying hashed passwords.
/// </summary>
internal sealed class PasswordHasher : IPasswordHasher, IPasswordHashChecker, IDisposable
{
private const KeyDerivationPrf Prf = KeyDerivationPrf.HMACSHA256;
private const int IterationCount = 10000;
private const int NumberOfBytesRequested = 256 / 8;
private const int SaltSize = 128 / 8;
private readonly RandomNumberGenerator _rng;
/// <summary>
/// Initializes a new instance of the <see cref="PasswordHasher"/> class.
/// </summary>
public PasswordHasher() => _rng = new RNGCryptoServiceProvider();
/// <inheritdoc />
public string HashPassword(Password password)
{
if (password is null)
{
throw new ArgumentNullException(nameof(password));
}
string hashedPassword = Convert.ToBase64String(HashPasswordInternal(password));
return hashedPassword;
}
/// <inheritdoc />
public bool HashesMatch(string passwordHash, string providedPassword)
{
if (passwordHash is null)
{
throw new ArgumentNullException(nameof(passwordHash));
}
if (providedPassword is null)
{
throw new ArgumentNullException(nameof(providedPassword));
}
byte[] decodedHashedPassword = Convert.FromBase64String(passwordHash);
if (decodedHashedPassword.Length == 0)
{
return false;
}
bool verified = VerifyPasswordHashInternal(decodedHashedPassword, providedPassword);
return verified;
}
/// <inheritdoc />
public void Dispose() => _rng.Dispose();
/// <summary>
/// Returns the bytes of the hash for the specified password.
/// </summary>
/// <param name="password">The password to be hashed.</param>
/// <returns>The bytes of the hash for the specified password.</returns>
private byte[] HashPasswordInternal(string password)
{
byte[] salt = GetRandomSalt();
byte[] subKey = KeyDerivation.Pbkdf2(password, salt, Prf, IterationCount, NumberOfBytesRequested);
byte[] outputBytes = new byte[salt.Length + subKey.Length];
Buffer.BlockCopy(salt, 0, outputBytes, 0, salt.Length);
Buffer.BlockCopy(subKey, 0, outputBytes, salt.Length, subKey.Length);
return outputBytes;
}
/// <summary>
/// Gets a randomly generated salt.
/// </summary>
/// <returns>The randomly generated salt.</returns>
private byte[] GetRandomSalt()
{
byte[] salt = new byte[SaltSize];
_rng.GetBytes(salt);
return salt;
}
/// <summary>
/// Verifies the bytes of the hashed password with the specified password.
/// </summary>
/// <param name="hashedPassword">The bytes of the hashed password.</param>
/// <param name="password">The password to verify with.</param>
/// <returns>True if the hashes match, otherwise false.</returns>
private static bool VerifyPasswordHashInternal(byte[] hashedPassword, string password)
{
try
{
byte[] salt = new byte[SaltSize];
Buffer.BlockCopy(hashedPassword, 0, salt, 0, salt.Length);
int subKeyLength = hashedPassword.Length - salt.Length;
if (subKeyLength < SaltSize)
{
return false;
}
byte[] expectedSubKey = new byte[subKeyLength];
Buffer.BlockCopy(hashedPassword, salt.Length, expectedSubKey, 0, expectedSubKey.Length);
byte[] actualSubKey = KeyDerivation.Pbkdf2(password, salt, Prf, IterationCount, subKeyLength);
return ByteArraysEqual(actualSubKey, expectedSubKey);
}
catch
{
return false;
}
}
/// <summary>
/// Returns true if the specified byte arrays are equal, otherwise false.
/// </summary>
/// <param name="a">The first byte array.</param>
/// <param name="b">The second byte array.</param>
/// <returns>True if the arrays are equal, otherwise false.</returns>
private static bool ByteArraysEqual(byte[] a, byte[] b)
{
if (a == null && b == null)
{
return true;
}
if (a == null || b == null || a.Length != b.Length)
{
return false;
}
bool areSame = true;
for (int i = 0; i < a.Length; i++)
{
areSame &= a[i] == b[i];
}
return areSame;
}
}
}