C#: message authentication with HMAC-SHA256
Compute a keyed tag so a receiver can detect tampering, and compare tags safely.
using System;
using System.Security.Cryptography;
using System.Text;
class Program {
static void Main() {
byte[] key = Encoding.UTF8.GetBytes("Jefe");
byte[] message = Encoding.UTF8.GetBytes("what do ya want for nothing?");
byte[] tampered = Encoding.UTF8.GetBytes("what do ya want for something?");
using var hmac = new HMACSHA256(key);
byte[] tag = hmac.ComputeHash(message);
Console.WriteLine(Convert.ToHexString(tag).ToLowerInvariant());
byte[] again = hmac.ComputeHash(message);
Console.WriteLine("same message valid: " + CryptographicOperations.FixedTimeEquals(tag, again));
byte[] forged = hmac.ComputeHash(tampered);
Console.WriteLine("tampered message valid: " + CryptographicOperations.FixedTimeEquals(tag, forged));
}
}5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 same message valid: True tampered message valid: False
Explanation
A plain hash lets anyone recompute a digest after changing a message. An HMAC mixes a secret key into the hash, so only someone who knows the key can produce a valid tag. The receiver recomputes the tag with the shared key and compares it with the one received. If they match, the message is authentic and unmodified.
The example uses the HMAC-SHA256 test vector from RFC 4231 with key Jefe. CryptographicOperations.FixedTimeEquals compares two byte arrays in time that does not depend on where they first differ, which prevents timing attacks. An ordinary equality check can leak how many bytes matched.
Explore the dry run
using System;using System.Security.Cryptography;using System.Text; class Program { static void Main() { byte[] key = Encoding.UTF8.GetBytes("Jefe"); byte[] message = Encoding.UTF8.GetBytes("what do ya want for nothing?"); byte[] tampered = Encoding.UTF8.GetBytes("what do ya want for something?"); using var hmac = new HMACSHA256(key); byte[] tag = hmac.ComputeHash(message); Console.WriteLine(Convert.ToHexString(tag).ToLowerInvariant()); byte[] again = hmac.ComputeHash(message); Console.WriteLine("same message valid: " + CryptographicOperations.FixedTimeEquals(tag, again)); byte[] forged = hmac.ComputeHash(tampered); Console.WriteLine("tampered message valid: " + CryptographicOperations.FixedTimeEquals(tag, forged)); }}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Create an HMAC engine bound to the secret key. | — | — | |
| The tag depends on both the message and the key. | tag = 32 bytes | — | |
| This matches the published RFC 4231 test value. | tag = 32 bytes | 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 | |
| The receiver recomputes the tag and compares in constant time. | tag = 32 bytes | 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 same message valid: True | |
| Changing the text changes the tag, so the forgery is detected. | tag = 32 bytes | 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 same message valid: True tampered message valid: False |
Cost and common mistakes
O(m) time for an m-byte message.
- The key must be secret and long enough. A short, guessable key like Jefe is only acceptable in a published test vector.
- An HMAC proves authenticity and integrity, not confidentiality: the message is still readable.
- Include a nonce or timestamp in the signed data if replaying an old valid message would be harmful.
Try it yourself
Why not use SHA256(key + message) instead of HMAC?
Reveal answer and reasoning
Hashes built this way are vulnerable to length-extension attacks. HMAC is designed to avoid them.