This commit is contained in:
masoodafar-web
2025-11-12 23:17:56 +03:30
parent 2fac0f4922
commit 0adb0713f3
7 changed files with 3205 additions and 2812 deletions

View File

@@ -19,6 +19,7 @@ public static class ConfigureServices
services.AddScoped<AuditableEntitySaveChangesInterceptor>();
services.AddScoped<ApplicationDbContextInitialiser>();
services.AddScoped<IGenerateJwtToken, GenerateJwtTokenService>();
services.AddScoped<IHashService, HashService>();
services.AddScoped<IApplicationDbContext>(p => p.GetRequiredService<ApplicationDbContext>());
if (configuration.GetValue<bool>("UseInMemoryDatabase"))
{

View File

@@ -0,0 +1,88 @@
using CMSMicroservice.Application.Common.Interfaces;
using System.Security.Cryptography;
using System.Text;
namespace CMSMicroservice.Infrastructure.Services;
public class HashService : IHashService
{
// PBKDF2 parameters
private const int SaltSize = 16; // 128-bit
private const int KeySize = 32; // 256-bit
private const int Iterations = 100_000; // strong enough default
private const string Pbkdf2Prefix = "PBKDF2"; // version marker
public string ComputeSha256Hex(string input)
{
using var sha = SHA256.Create();
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes);
}
public bool VerifySha256Hex(string input, string? expectedHexHash)
{
if (string.IsNullOrEmpty(expectedHexHash)) return false;
var actual = ComputeSha256Hex(input);
return actual.Equals(expectedHexHash, StringComparison.OrdinalIgnoreCase);
}
public string ComputeHmacSha256Hex(string input, string secret)
{
using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = h.ComputeHash(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(hash);
}
public bool VerifyHmacSha256Hex(string input, string? expectedHexHash, string secret)
{
if (string.IsNullOrEmpty(expectedHexHash)) return false;
var actual = ComputeHmacSha256Hex(input, secret);
return actual.Equals(expectedHexHash, StringComparison.OrdinalIgnoreCase);
}
public string HashPassword(string password)
{
// generate salt
Span<byte> salt = stackalloc byte[SaltSize];
RandomNumberGenerator.Fill(salt);
// derive key
var pbkdf2 = new Rfc2898DeriveBytes(password, salt.ToArray(), Iterations, HashAlgorithmName.SHA256);
var derived = pbkdf2.GetBytes(KeySize);
// encode: PBKDF2$<iter>$<saltBase64>$<hashBase64>
return string.Join('$', Pbkdf2Prefix, Iterations.ToString(), Convert.ToBase64String(salt.ToArray()), Convert.ToBase64String(derived));
}
public bool VerifyPassword(string password, string? storedHash)
{
if (string.IsNullOrWhiteSpace(storedHash)) return false;
// legacy: raw SHA-256 hex
if (!storedHash.Contains('$'))
{
return VerifySha256Hex(password, storedHash);
}
var parts = storedHash.Split('$');
if (parts.Length != 4 || !string.Equals(parts[0], Pbkdf2Prefix, StringComparison.Ordinal))
return false;
if (!int.TryParse(parts[1], out var iterations) || iterations <= 0) return false;
byte[] salt;
byte[] expectedKey;
try
{
salt = Convert.FromBase64String(parts[2]);
expectedKey = Convert.FromBase64String(parts[3]);
}
catch
{
return false;
}
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256);
var actualKey = pbkdf2.GetBytes(expectedKey.Length);
// fixed-time comparison
return CryptographicOperations.FixedTimeEquals(actualKey, expectedKey);
}
}