update
This commit is contained in:
@@ -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"))
|
||||
{
|
||||
|
||||
88
src/CMSMicroservice.Infrastructure/Services/HashService.cs
Normal file
88
src/CMSMicroservice.Infrastructure/Services/HashService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user