using System.Security.Cryptography; namespace Microsoft.SelfService.Portal.Core.API.Services { public static class ApiClientSecretHasher { private const int SaltSize = 16; private const int KeySize = 32; private const int DefaultIterations = 100_000; public static string HashSecret(string secret) { var salt = RandomNumberGenerator.GetBytes(SaltSize); var hash = Rfc2898DeriveBytes.Pbkdf2(secret, salt, DefaultIterations, HashAlgorithmName.SHA256, KeySize); return string.Join( '.', "PBKDF2-SHA256", DefaultIterations.ToString(), Convert.ToBase64String(salt), Convert.ToBase64String(hash)); } public static bool VerifySecret(string secret, string storedHash) { var parts = storedHash.Split('.'); if (parts.Length != 4 || parts[0] != "PBKDF2-SHA256") { return false; } if (!int.TryParse(parts[1], out var iterations)) { return false; } var salt = Convert.FromBase64String(parts[2]); var expectedHash = Convert.FromBase64String(parts[3]); var actualHash = Rfc2898DeriveBytes.Pbkdf2(secret, salt, iterations, HashAlgorithmName.SHA256, expectedHash.Length); return CryptographicOperations.FixedTimeEquals(actualHash, expectedHash); } internal static string HashSecretForDemoData(string secret, string saltText) { var salt = System.Text.Encoding.UTF8.GetBytes(saltText); Array.Resize(ref salt, SaltSize); var hash = Rfc2898DeriveBytes.Pbkdf2(secret, salt, DefaultIterations, HashAlgorithmName.SHA256, KeySize); return string.Join( '.', "PBKDF2-SHA256", DefaultIterations.ToString(), Convert.ToBase64String(salt), Convert.ToBase64String(hash)); } } }