journal/Journal.WebGateway/Security/GatewayPasswordHasher.cs
stan44 7562cf6fad Add gateway root adoption and mobile polish
- Add Tauri commands to inspect and adopt the gateway repo root
- Retry locked vault commands by prompting for unlock
- Improve mobile layout, editor mode toggles, and settings UI
2026-03-30 00:00:25 -05:00

70 lines
2.0 KiB
C#

using System.Security.Cryptography;
using System.Text;
namespace Journal.WebGateway.Security;
public static class GatewayPasswordHasher
{
private const string AlgorithmName = "PBKDF2-SHA256";
private const int SaltSize = 16;
private const int KeySize = 32;
private const int IterationCount = 600_000;
public static string HashPassword(string password)
{
ArgumentException.ThrowIfNullOrWhiteSpace(password);
var salt = RandomNumberGenerator.GetBytes(SaltSize);
var key = Rfc2898DeriveBytes.Pbkdf2(
Encoding.UTF8.GetBytes(password),
salt,
IterationCount,
HashAlgorithmName.SHA256,
KeySize);
return string.Join(
'$',
AlgorithmName,
IterationCount.ToString(System.Globalization.CultureInfo.InvariantCulture),
Convert.ToBase64String(salt),
Convert.ToBase64String(key));
}
public static bool VerifyPassword(string password, string passwordHash)
{
if (string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(passwordHash))
return false;
var parts = passwordHash.Split('$', StringSplitOptions.None);
if (parts.Length != 4)
return false;
if (!string.Equals(parts[0], AlgorithmName, 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 (FormatException)
{
return false;
}
var actualKey = Rfc2898DeriveBytes.Pbkdf2(
Encoding.UTF8.GetBytes(password),
salt,
iterations,
HashAlgorithmName.SHA256,
expectedKey.Length);
return CryptographicOperations.FixedTimeEquals(actualKey, expectedKey);
}
}