HMAC
HMAC (Hash-based Message Authentication Code) — Simple Definition
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
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