maksit-core/src/MaksIT.Core/Security/PasswordHasher.cs

78 lines
2.1 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace MaksIT.Core.Security;
public static class PasswordHasher {
private static byte[] CreateSaltBytes() {
byte[] randomBytes = new byte[16];
using (var generator = RandomNumberGenerator.Create()) {
generator.GetBytes(randomBytes);
}
return randomBytes;
}
private static string CreateHash(string value, byte[] saltBytes, string pepper) {
// Combine password and pepper
var valueWithPepper = value + pepper;
var valueBytes = KeyDerivation.Pbkdf2(
password: valueWithPepper,
salt: saltBytes,
prf: KeyDerivationPrf.HMACSHA512,
iterationCount: 100_000, // Increased iteration count
numBytesRequested: 256 / 8);
return Convert.ToBase64String(valueBytes);
}
public static bool TryCreateSaltedHash(
string value,
string pepper,
[NotNullWhen(true)] out (string Salt, string Hash)? saltedHash,
[NotNullWhen(false)] out string? errorMessage
) {
try {
var saltBytes = CreateSaltBytes();
var hash = CreateHash(value, saltBytes, pepper);
var salt = Convert.ToBase64String(saltBytes);
saltedHash = (salt, hash);
errorMessage = null;
return true;
}
catch (Exception ex) {
saltedHash = null;
errorMessage = ex.Message;
return false;
}
}
public static bool TryValidateHash(
string value,
string salt,
string hash,
string pepper,
[NotNullWhen(true)] out bool isValid,
[NotNullWhen(false)] out string? errorMessage
) {
try {
var saltBytes = Convert.FromBase64String(salt);
var hashToCompare = CreateHash(value, saltBytes, pepper);
isValid = CryptographicOperations.FixedTimeEquals(
Convert.FromBase64String(hashToCompare),
Convert.FromBase64String(hash)
);
errorMessage = null;
return true;
}
catch (Exception ex) {
isValid = false;
errorMessage = ex.Message;
return false;
}
}
}