using System.Security.Cryptography; using System.Text; namespace backend.Devices; public class DeviceTokenService { public (string Plaintext, string Hash) Generate() { Span bytes = stackalloc byte[32]; RandomNumberGenerator.Fill(bytes); var plaintext = Base64UrlEncode(bytes); return (plaintext, Hash(plaintext)); } public string Hash(string plaintext) { Span hash = stackalloc byte[32]; SHA256.HashData(Encoding.UTF8.GetBytes(plaintext), hash); return Convert.ToHexString(hash); } public bool Verify(string plaintext, string expectedHash) { var actual = Hash(plaintext); return CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(actual), Encoding.UTF8.GetBytes(expectedHash)); } private static string Base64UrlEncode(ReadOnlySpan bytes) => Convert.ToBase64String(bytes) .TrimEnd('=') .Replace('+', '-') .Replace('/', '_'); }