using System.Security.Cryptography; namespace XFEExtension.NetCore.ServerInteractive.Models.UserModels; /// 版本化 PBKDF2 密码凭据。 public sealed class PasswordCredential { public string Algorithm { get; set; } = "PBKDF2-HMAC-SHA256"; public int Version { get; set; } = 1; public string Salt { get; set; } = string.Empty; public int Iterations { get; set; } = PasswordHasher.DefaultIterations; public string Hash { get; set; } = string.Empty; } /// 使用 .NET 标准密码学 API 的密码哈希器。 public static class PasswordHasher { public const int DefaultIterations = 600_000; private const int SaltSize = 16; private const int HashSize = 32; private const int MaximumIterations = 5_000_000; public static PasswordCredential Hash(string password, int iterations = DefaultIterations) { ArgumentException.ThrowIfNullOrEmpty(password); if (iterations is < 100_000 or > MaximumIterations) throw new ArgumentOutOfRangeException(nameof(iterations)); var salt = RandomNumberGenerator.GetBytes(SaltSize); var hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, HashSize); return new() { Salt = Convert.ToBase64String(salt), Iterations = iterations, Hash = Convert.ToBase64String(hash) }; } public static bool Verify(string password, PasswordCredential credential) { ArgumentNullException.ThrowIfNull(credential); if (credential.Algorithm != "PBKDF2-HMAC-SHA256" || credential.Version != 1 || credential.Iterations is < 100_000 or > MaximumIterations) return false; try { var salt = Convert.FromBase64String(credential.Salt); var expected = Convert.FromBase64String(credential.Hash); if (salt.Length != SaltSize || expected.Length != HashSize) return false; var actual = Rfc2898DeriveBytes.Pbkdf2(password, salt, credential.Iterations, HashAlgorithmName.SHA256, expected.Length); return CryptographicOperations.FixedTimeEquals(actual, expected); } catch (FormatException) { return false; } } internal static bool FixedTimePlainTextEquals(string supplied, string stored) { var suppliedHash = SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(supplied)); var storedHash = SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(stored)); return CryptographicOperations.FixedTimeEquals(suppliedHash, storedHash); } }