← Back to list

HMAC

HMAC (Hash-based Message Authentication Code) — Simple Definition

Karim Samir in easydotnet · 2025-12-02 12:33 · 0 claps · 2.6 min read
#hmac
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3

HMAC

HMAC (Hash-based Message Authentication Code) — Simple Definition

HMAC is a security method where:

— Two systems share a secret key (not visible to anyone else) — One system creates a signature by hashing the request + secret key — The other system recalculates the same signature — If both signatures match → the request is authentic and untampered

HMAC flow (VERY IMPORTANT)

✔ Sender (API A)

  • Builds the canonical string
  • Computes signature = HMAC(secret, data)
  • Sends signature only
  • Does NOT compare signatures

✔ Receiver (API B)

  • Rebuilds the canonical string
  • Computes expected = HMAC(secret, data)
  • Compares expected == receivedSignature
  • Accepts or rejects request

HMAC FLOW

HMAC FLOW

CODE : Two Apis , Sender and receiver

Receiver :

HmacAuthMiddleware.cs

public class HmacAuthMiddleware
{
    private const string SecretKey = "XRpjBq&G&68KWd#9TCxGmxzJbn7vNdKHPJV4&&R";  // <== IMPORTANT: in real life, this MUST be stored in appsettings.json, KeyVault, or similar
    private const long TimeTolerance = 300; // 5 minutes
    private readonly RequestDelegate _next;

    public HmacAuthMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue("X-API-Key", out var apiKey) ||
            !context.Request.Headers.TryGetValue("X-Timestamp", out var timestamp) ||
            !context.Request.Headers.TryGetValue("X-Nonce", out var nonce) ||
            !context.Request.Headers.TryGetValue("X-HMAC-Signature", out var signature))
        {
            await WriteUnauthorized(context, "Missing headers");
            return;
        }

        if (!long.TryParse(timestamp, out long requestTime) ||
            Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - requestTime) > TimeTolerance)
        {
            await WriteUnauthorized(context, "Invalid timestamp");
            return;
        }

        // Enable buffering for body rewind
        context.Request.EnableBuffering();
        var body = await new StreamReader(context.Request.Body, Encoding.UTF8).ReadToEndAsync();
        context.Request.Body.Position = 0; // Reset for downstream

        var computedSignature = ComputeSignature(body, timestamp!, nonce!);

        if (computedSignature != signature)
        {
            await WriteUnauthorized(context, "Invalid signature");
            return;
        }

        await _next(context);
    }

    private static string ComputeSignature(string data, string timestamp, string nonce)
    {
        var message = $"{timestamp}:{nonce}:{data}";

        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(SecretKey));

        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));

        return Convert.ToBase64String(hash);
    }

    private static async Task WriteUnauthorized(HttpContext context, string message)
    {
        context.Response.StatusCode = 401;

        await context.Response.WriteAsync(message);
    }
}

Program.cs

app.UseMiddleware<HmacAuthMiddleware>();

API:

[HttpPost("receiver")]
public async Task<IActionResult> sendGet()
{
    try
    {
        await Task.Delay(10); // Simulate some async work
        return Ok("Request verified!");
    }
    catch (Exception ex)
    {

        throw;
    }
}

Sender

SenderService.cs

public class SenderService
{
    private const string SecretKey = "XRpjBq&G&68KWd#9TCxGmxzJbn7vNdKHPJV4&&R"; // <== IMPORTANT: in real=life, this must be stored in appsettings.json, KeyVault, or similar
    private const string ApiKey = "4f7a49a7-6ce9-4a2f-82f0-a14e3954617e"; // <== IMPORTANT: same as above
    private const string ReceiverUrl = "https://localhost:7066/api/receiver";

    public static async Task SendRequestAsync()
    {
        try
        {
            using var client = new HttpClient();

            var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();

            const string body = "{\"message\":\"Hello, Receiver!\"}";

            var nonce = Guid.NewGuid().ToString("N");

            var signature = ComputeSignature(body, timestamp, nonce);

            var request = new HttpRequestMessage(HttpMethod.Post, ReceiverUrl)
            {
                Content = new StringContent(body, Encoding.UTF8, "application/json")
            };

            request.Headers.Add("X-API-Key", ApiKey);
            request.Headers.Add("X-Timestamp", timestamp);
            request.Headers.Add("X-Nonce", nonce);
            request.Headers.Add("X-HMAC-Signature", signature);
            request.Headers.Add("X-Key-Version", "1"); // For future key rotation

            // The request is sent here, you might want to debug the middleware in the receiver area
            var response = await client.SendAsync(request);
            Console.WriteLine($"Sender got: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}");
        }
        catch (Exception ex)
        {

            throw;
        }
    }

    private static string ComputeSignature(string data, string timestamp, string nonce)
    {
        var message = $"{timestamp}:{nonce}:{data}";

        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(SecretKey));

        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));

        return Convert.ToBase64String(hash);
    }
}

API

[HttpGet("send")]
public async Task<IActionResult> sendGet()
{
    try
    {
        await SenderService.SendRequestAsync();
        return Ok("Request sent from WeatherForecastController");
    }
    catch (Exception ex)
    {

        throw;
    }
}

👉 Two systems share a secret key (not visible to anyone else) 👉 One system creates a signature by hashing the request + secret key 👉 The other system recalculates the same signature 👉 If both signatures match → the request is authentic and untampered


메타데이터
post_id
c48b444e1c4d
slug
hmac-c48b444e1c4d
url
https://medium.com/easydotnet/hmac-c48b444e1c4d
canonical_url
https://medium.com/easydotnet/hmac-c48b444e1c4d
author_url
https://medium.com/@karim.samir
status
ok
fetched_at
2026-06-17 08:20:12