using System.Security.Cryptography;
using System.Runtime.CompilerServices;
var operation = "generate-key";
var hash = ComputeHash(args[1], args[2]);
var isMatch = Compare(args[1], args[2], args[3]);
Console.WriteLine(isMatch);
case "compare-framework":
var isMatchDotNet = CompareFramework(args[1], args[2], args[3]);
Console.WriteLine(isMatchDotNet);
Console.WriteLine("Invalid command");
static string GenerateKey()
byte[] randomNumber = new Byte[64];
using (var randomNumberGenerator = RandomNumberGenerator.Create())
randomNumberGenerator.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
static string ComputeHash(string key, string message)
using (HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key))) {
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToBase64String(hash);
static bool Compare(string key, string message, string expectedHash) {
var computedHash = ComputeHash(key, message);
var computedHashBytes = Convert.FromBase64String(computedHash);
var expectedHashBytes = Convert.FromBase64String(expectedHash);
return CryptographicOperations.FixedTimeEquals(computedHashBytes, expectedHashBytes);
static bool CompareFramework(string key, string message, string expectedHash) {
var computedHash = ComputeHash(key, message);
return ConstantTimeStringComapre(computedHash, expectedHash);
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
static bool ConstantTimeStringComapre(string str1, string str2)
if (str1.Length != str2.Length)
for (int index = 0; index < str1.Length; ++index) {
result |= str1[index] ^ str2[index];