Files
nexus/backend/Services/PasswordSecurity.cs
T
bao eeb6174de0 Initial commit: Nexus Mission Control Platform
- ASP.NET Core 10 Backend (JWT Auth, Agent config API)
- Vue 3 Frontend (Dashboard, Team, Agents, Config Editor)
- PostgreSQL Database
- Docker Compose setup
- Mission Control Dashboard redesign
2026-06-09 16:31:56 +02:00

68 lines
2.2 KiB
C#

using System.Security.Cryptography;
using System.Text;
namespace Nexus.Api.Services;
public static class PasswordSecurity
{
private const int Iterations = 210_000;
private const int SaltSize = 16;
private const int HashSize = 32;
private const string Version = "v1";
public static string Hash(string password)
{
ArgumentException.ThrowIfNullOrWhiteSpace(password);
var salt = RandomNumberGenerator.GetBytes(SaltSize);
var hash = Rfc2898DeriveBytes.Pbkdf2(
password,
salt,
Iterations,
HashAlgorithmName.SHA256,
HashSize);
return string.Join('.', Version, Iterations, Convert.ToBase64String(salt), Convert.ToBase64String(hash));
}
public static bool Verify(string password, string encodedHash, out bool needsUpgrade)
{
needsUpgrade = false;
if (string.IsNullOrEmpty(password) || string.IsNullOrEmpty(encodedHash)) return false;
var parts = encodedHash.Split('.');
if (parts.Length == 4 && parts[0] == Version && int.TryParse(parts[1], out var iterations))
{
try
{
var salt = Convert.FromBase64String(parts[2]);
var expected = Convert.FromBase64String(parts[3]);
var actual = Rfc2898DeriveBytes.Pbkdf2(
password,
salt,
iterations,
HashAlgorithmName.SHA256,
expected.Length);
needsUpgrade = iterations < Iterations;
return CryptographicOperations.FixedTimeEquals(actual, expected);
}
catch (FormatException)
{
return false;
}
}
if (encodedHash.Length == 64 && encodedHash.All(Uri.IsHexDigit))
{
var legacy = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(password)));
needsUpgrade = true;
return CryptographicOperations.FixedTimeEquals(
Encoding.ASCII.GetBytes(legacy),
Encoding.ASCII.GetBytes(encodedHash.ToUpperInvariant()));
}
return false;
}
}