Table of Contents

MAC Algorithms Reference

This page provides detailed documentation for all Message Authentication Code (MAC) algorithms implemented in the CryptoHives.Foundation.Security.Cryptography package.

Namespace

using CryptoHives.Foundation.Security.Cryptography.Mac;

Overview

Message Authentication Codes (MACs) provide both data integrity and authenticity verification. Unlike simple hash functions, MACs require a secret key, ensuring that only parties with the key can generate or verify the authentication tag.

MAC vs Hash

Feature Hash MAC
Secret key required No Yes
Data integrity Yes Yes
Data authenticity No Yes
Use case Fingerprinting Authentication

KMAC128

KMAC128 is the Keccak Message Authentication Code with 128-bit security, defined in NIST SP 800-185.

Class Declaration

public sealed class KMac128 : HashAlgorithm

Properties

Property Value
Security 128 bits
Output Size Variable (default 32 bytes)
Key Size Any length
Block Size 168 bytes (rate)

Constructor

public KMac128(byte[] key, int outputBytes = 32, string customization = "")

Parameters:

  • key - The secret key (any length, cannot be empty)
  • outputBytes - Desired output size in bytes (default: 32)
  • customization - Optional customization string for domain separation

Factory Method

public static KMac128 Create(byte[] key, int outputBytes = 32, string customization = "")

Usage Examples

byte[] key = new byte[32];
RandomNumberGenerator.Fill(key);
byte[] message = Encoding.UTF8.GetBytes("Hello, World!");

// Basic KMAC128
using var kmac = KMac128.Create(key, outputBytes: 32);
Span<byte> mac = stackalloc byte[32];
kmac.TryComputeHash(message, mac, out _);

// With customization string
using var kmacCustom = KMac128.Create(key, outputBytes: 32, customization: "MyApp v1.0");
Span<byte> macCustom = stackalloc byte[32];
kmacCustom.TryComputeHash(message, macCustom, out _);

// Variable output length
using var kmacLong = KMac128.Create(key, outputBytes: 64);
Span<byte> longMac = stackalloc byte[64];
kmacLong.TryComputeHash(message, longMac, out _);

Incremental Usage

using var kmac = KMac128.Create(key, outputBytes: 32, customization: "");

// Process data in chunks — zero allocations
kmac.AppendData(chunk1);
kmac.AppendData(chunk2);
kmac.AppendData(chunk3);

Span<byte> mac = stackalloc byte[32];
kmac.TryGetHashAndReset(mac, out _);

KMAC256

KMAC256 is the Keccak Message Authentication Code with 256-bit security, defined in NIST SP 800-185.

Class Declaration

public sealed class KMac256 : HashAlgorithm

Properties

Property Value
Security 256 bits
Output Size Variable (default 64 bytes)
Key Size Any length
Block Size 136 bytes (rate)

Constructor

public KMac256(byte[] key, int outputBytes = 64, string customization = "")

Parameters:

  • key - The secret key (any length, cannot be empty)
  • outputBytes - Desired output size in bytes (default: 64)
  • customization - Optional customization string for domain separation

Factory Method

public static KMac256 Create(byte[] key, int outputBytes = 64, string customization = "")

Usage Examples

byte[] key = new byte[32];
RandomNumberGenerator.Fill(key);
byte[] message = Encoding.UTF8.GetBytes("Hello, World!");

// Basic KMAC256
using var kmac = KMac256.Create(key, outputBytes: 64);
Span<byte> mac = stackalloc byte[64];
kmac.TryComputeHash(message, mac, out _);

// With customization string for domain separation
using var kmacCustom = KMac256.Create(key, outputBytes: 64, customization: "Session Authentication");
Span<byte> macCustom = stackalloc byte[64];
kmacCustom.TryComputeHash(message, macCustom, out _);

HMAC (Hash-based Message Authentication Code)

HMAC is the most widely used MAC construction, defined in RFC 2104 and FIPS 198-1. It works with any cryptographic hash function and is used extensively in TLS, SSH, IPsec, and OAuth.

IMac Interface

All new MAC types implement the IMac interface for consistent API:

public interface IMac : IDisposable
{
    string AlgorithmName { get; }
    int MacSize { get; }
    void Update(ReadOnlySpan<byte> input);
    void Finalize(Span<byte> destination);
    void Reset();
}

Available HMAC Variants

Class Hash MAC Size Security Status
HmacSha256 SHA-256 32 bytes 256 bits ✅ Recommended
HmacSha384 SHA-384 48 bytes 384 bits ✅ Recommended
HmacSha512 SHA-512 64 bytes 512 bits ✅ Recommended
HmacSha3_256 SHA3-256 32 bytes 256 bits ✅ Cross-platform
HmacSha1 SHA-1 20 bytes 160 bits ⚠️ Legacy
HmacMd5 MD5 16 bytes 128 bits ⚠️ Legacy

Constructor

public HmacSha256(byte[] key)

Parameters:

  • key - The secret key (any length; keys longer than the hash block size are hashed first)

Factory Method

public static HmacSha256 Create(byte[] key)

Static One-Shot

public static byte[] Hash(byte[] key, byte[] data)

Usage Examples

byte[] key = new byte[32];
RandomNumberGenerator.Fill(key);
byte[] message = Encoding.UTF8.GetBytes("Hello, World!");

// One-shot API
byte[] tag = HmacSha256.Hash(key, message);

// Instance-based API
using var hmac = HmacSha256.Create(key);
byte[] mac = hmac.ComputeHash(message);

// Streaming API (IMac interface)
using var hmac = HmacSha256.Create(key);
hmac.Update(chunk1);
hmac.Update(chunk2);
byte[] result = new byte[hmac.MacSize];
hmac.Finalize(result);

// Reuse with same key
hmac.Reset();
hmac.Update(newData);
hmac.Finalize(result);

HMAC-SHA3-256 (Cross-Platform)

Unlike .NET's built-in HMACSHA3_256 which requires Windows 11+ or OpenSSL 1.1.1+, the CryptoHives implementation works on all platforms:

using var hmac = HmacSha3_256.Create(key);
byte[] mac = hmac.ComputeHash(message);

Generic HMAC with Any Hash

The HmacCore base class works with any HashAlgorithm from the library. Create custom HMAC variants by subclassing:

public sealed class HmacSm3 : HmacCore
{
    public HmacSm3(byte[] key) : base("HMAC-SM3", SM3.Create(), SM3.Create(), key) { }
}

AES-CMAC (Cipher-based MAC)

AES-CMAC is a MAC based on the AES block cipher, defined in NIST SP 800-38B and RFC 4493. Unlike HMAC, it computes the tag in a single pass and does not require a hash function.

Note: .NET does not provide a portable CMAC implementation. This is a CryptoHives differentiator.

Class Declaration

public sealed class AesCmac : IMac

Properties

Property Value
MAC Size 128 bits (16 bytes)
Key Sizes 128, 192, or 256 bits
Block Size 128 bits (AES)
Hardware Accel. AES-NI when available

Constructor

public AesCmac(byte[] key)
public AesCmac(ReadOnlySpan<byte> key)

Parameters:

  • key - The secret key (16, 24, or 32 bytes for AES-128/192/256)

Factory Method

public static AesCmac Create(byte[] key)

Static One-Shot

public static byte[] Hash(byte[] key, byte[] data)

Usage Examples

byte[] key = new byte[16]; // AES-128
RandomNumberGenerator.Fill(key);
byte[] message = Encoding.UTF8.GetBytes("Hello, World!");

// One-shot API
byte[] tag = AesCmac.Hash(key, message);

// Instance-based API
using var cmac = AesCmac.Create(key);
byte[] mac = cmac.ComputeHash(message);

// Streaming API
using var cmac = AesCmac.Create(key);
cmac.Update(chunk1);
cmac.Update(chunk2);
byte[] result = new byte[cmac.MacSize];
cmac.Finalize(result);

AES-256 CMAC

byte[] key = new byte[32]; // AES-256
RandomNumberGenerator.Fill(key);

using var cmac = AesCmac.Create(key);
byte[] mac = cmac.ComputeHash(message);

AES-GMAC (Galois MAC)

AES-GMAC is the authentication-only mode of AES-GCM, defined in NIST SP 800-38D. It produces a 128-bit tag using the Galois field multiplication (GHASH) used in GCM.

Note: .NET does not provide a standalone GMAC class. This is a CryptoHives differentiator.

Class Declaration

public sealed class AesGmac : IDisposable

Properties

Property Value
MAC Size 128 bits (16 bytes)
Key Sizes 128, 192, or 256 bits
Nonce Size 96 bits (12 bytes)
Hardware Accel. AES-NI + PCLMULQDQ

Constructor

public AesGmac(ReadOnlySpan<byte> key)

Parameters:

  • key - The secret key (16, 24, or 32 bytes)

Factory Method

public static AesGmac Create(byte[] key)

Usage Examples

byte[] key = new byte[16];
RandomNumberGenerator.Fill(key);
byte[] nonce = new byte[12]; // MUST be unique per message
RandomNumberGenerator.Fill(nonce);
byte[] data = Encoding.UTF8.GetBytes("authenticated data");

// Compute tag
using var gmac = AesGmac.Create(key);
byte[] tag = gmac.ComputeTag(nonce, data);

// Verify tag
bool valid = gmac.VerifyTag(nonce, data, tag);

Important: Nonce Requirements

Never reuse a nonce with the same key. Each GMAC invocation must use a unique 12-byte nonce. Nonce reuse completely compromises the authentication guarantee.

// CORRECT: Generate a fresh nonce for each message
byte[] nonce = new byte[12];
RandomNumberGenerator.Fill(nonce);

// WRONG: Reusing the same nonce with the same key
// byte[] nonce = new byte[12]; // reused - INSECURE!

Poly1305

Poly1305 is a high-speed one-time authenticator designed by Daniel J. Bernstein, defined in RFC 8439. It is the MAC component of ChaCha20-Poly1305 and XChaCha20-Poly1305 AEAD constructions.

Note: .NET does not provide a standalone Poly1305 class. This is a CryptoHives differentiator.

Class Declaration

public sealed class Poly1305Mac : IMac

Properties

Property Value
MAC Size 128 bits (16 bytes)
Key Size 256 bits (32 bytes)
Block Size 128 bits (16 bytes)
One-Time Key Yes — key must be unique per message

Constructor

public Poly1305Mac(ReadOnlySpan<byte> key)

Parameters:

  • key - A 32-byte one-time key. Must be unique for every message.

Factory Method

public static Poly1305Mac Create(byte[] key)
public static Poly1305Mac Create(ReadOnlySpan<byte> key)

Static One-Shot

public static byte[] Hash(byte[] key, byte[] data)

Usage Examples

using CryptoHives.Foundation.Security.Cryptography.Mac;

byte[] key = new byte[32]; // Must be exactly 32 bytes, unique per message
RandomNumberGenerator.Fill(key);
byte[] message = Encoding.UTF8.GetBytes("Hello, World!");

// One-shot API
byte[] tag = Poly1305Mac.Hash(key, message);

// Instance-based API
using var mac = Poly1305Mac.Create(key);
byte[] result = mac.ComputeHash(message);

// Streaming API (IMac interface)
using var mac = Poly1305Mac.Create(key);
mac.Update(chunk1);
mac.Update(chunk2);
byte[] tag = new byte[mac.MacSize]; // 16 bytes
mac.Finalize(tag);

Important: Key Requirements

The 32-byte key must be used for exactly one message. Reusing a key across multiple messages allows an attacker to forge tags. In practice, the key is derived from a session key and nonce:

// Typical pattern: derive Poly1305 key from ChaCha20 key stream
// (This is done internally by ChaCha20-Poly1305)
byte[] sessionKey = ...; // 256-bit session key
byte[] nonce = ...;      // 96-bit nonce

// Generate one-time key from ChaCha20 block 0
byte[] oneTimeKey = ChaCha20KeyStream(sessionKey, nonce, blockCounter: 0);
using var mac = Poly1305Mac.Create(oneTimeKey);

Reset Behavior

Calling Reset() restores the accumulator to the initial state with the same key. This allows computing multiple MACs with the same key (for testing or if the one-time key property is managed externally):

using var mac = Poly1305Mac.Create(key);

mac.Update(message1);
byte[] tag1 = new byte[16];
mac.Finalize(tag1);

mac.Reset(); // Resets accumulator, same key
mac.Update(message2);
byte[] tag2 = new byte[16];
mac.Finalize(tag2);

BLAKE2 MAC

BLAKE2b and BLAKE2s support built-in keyed hashing mode for message authentication.

Blake2b Keyed Mode

public static Blake2b Create(byte[]? key = null, int hashSize = 64)

Properties:

  • Security: Up to 256 bits (depends on key and output size)
  • Key Size: 1-64 bytes
  • Output Size: 1-64 bytes

Usage:

byte[] key = new byte[32]; // Up to 64 bytes
RandomNumberGenerator.Fill(key);

using var blake2b = Blake2b.Create(key: key, hashSize: 32);
Span<byte> mac = stackalloc byte[32];
blake2b.TryComputeHash(message, mac, out _);

Blake2s Keyed Mode

public static Blake2s Create(byte[]? key = null, int hashSize = 32)

Properties:

  • Security: Up to 128 bits (depends on key and output size)
  • Key Size: 1-32 bytes
  • Output Size: 1-32 bytes

Usage:

byte[] key = new byte[16]; // Up to 32 bytes
RandomNumberGenerator.Fill(key);

using var blake2s = Blake2s.Create(key: key, hashSize: 16);
Span<byte> mac = stackalloc byte[16];
blake2s.TryComputeHash(message, mac, out _);

BLAKE3 MAC

BLAKE3 provides a dedicated keyed hashing mode for message authentication.

Blake3 Keyed Mode

public static Blake3 CreateKeyed(byte[] key, int outputBytes = 32)

Properties:

  • Security: 128 bits
  • Key Size: Exactly 32 bytes
  • Output Size: Variable (default 32 bytes)

Usage:

byte[] key = new byte[32]; // Must be exactly 32 bytes
RandomNumberGenerator.Fill(key);

// Standard 32-byte MAC
using var blake3 = Blake3.CreateKeyed(key);
Span<byte> mac = stackalloc byte[32];
blake3.TryComputeHash(message, mac, out _);

// Extended 64-byte MAC
using var blake3Long = Blake3.CreateKeyed(key, outputBytes: 64);
Span<byte> longMac = stackalloc byte[64];
blake3Long.TryComputeHash(message, longMac, out _);

BLAKE3 Key Derivation

BLAKE3 also supports key derivation from a context string and input key material:

public static Blake3 CreateDeriveKey(string context, int outputBytes = 32)

Usage:

string context = "MyApp 2025-01-01 encryption key";
byte[] inputKeyMaterial = ...; // Your master key or password-derived key

using var blake3 = Blake3.CreateDeriveKey(context);
Span<byte> derivedKey = stackalloc byte[32];
blake3.TryComputeHash(inputKeyMaterial, derivedKey, out _);

Algorithm Comparison

Security Levels

Algorithm Security Strength Notes
HMAC-SHA-512 512 bits Maximum HMAC security
HMAC-SHA-256 256 bits Most widely used, recommended
KMAC256 256 bits Highest security, NIST approved
HMAC-SHA3-256 256 bits Cross-platform SHA-3 HMAC
Poly1305 128 bits Ultra-fast one-time authenticator
AES-CMAC 128 bits Cipher-based, single-pass
AES-GMAC 128 bits Galois field, nonce-required
KMAC128 128 bits Good security, NIST approved
BLAKE3 keyed 128 bits High performance
BLAKE2b keyed Up to 256 bits Depends on key/output size
BLAKE2s keyed Up to 128 bits Good for embedded systems

Performance

Algorithm Relative Speed Best For
Poly1305 Fastest AEAD constructions, one-time auth
BLAKE3 keyed Very fast High-throughput applications
BLAKE2b keyed Very fast General purpose on 64-bit
AES-GMAC Very fast (AES-NI) When nonce management is feasible
AES-CMAC Fast (AES-NI) Protocol compliance (EAP, 802.11i)
HMAC-SHA-256 Moderate General purpose, widest compatibility
HMAC-SHA-512 Moderate Maximum security on 64-bit
HMAC-SHA3-256 Moderate Cross-platform SHA-3
BLAKE2s keyed Fast 32-bit and embedded systems
KMAC256 Moderate Maximum security
KMAC128 Moderate NIST compliance

Feature Comparison

Feature HMAC AES-CMAC AES-GMAC Poly1305 KMAC BLAKE2 BLAKE3
Variable output
Customization string
Nonce required ❌¹
One-time key required
NIST approved
.NET built-in Partial
Key derivation
Arbitrary key size
Hardware accelerated

¹ Poly1305 does not use a nonce directly, but its key must be unique per message (typically derived from a nonce via ChaCha20).


Best Practices

Key Generation

Always use cryptographically secure random number generators for key generation:

byte[] key = new byte[32];
RandomNumberGenerator.Fill(key);

Key Storage

  • Never hardcode keys in source code
  • Use secure key storage (Azure Key Vault, AWS KMS, etc.)
  • Rotate keys periodically

Domain Separation

Use customization strings (KMAC) or different contexts (BLAKE3) to separate different uses of the same key:

// Authentication for different message types
using var kmacAuth = KMac256.Create(key, customization: "Auth");
using var kmacEncrypt = KMac256.Create(key, customization: "Encrypt");

// BLAKE3 key derivation with context
using var authKey = Blake3.CreateDeriveKey("MyApp Auth Key");
using var encKey = Blake3.CreateDeriveKey("MyApp Encryption Key");

Verification

When verifying MACs, use constant-time comparison to prevent timing attacks:

using System.Security.Cryptography;

bool VerifyMac(byte[] expected, byte[] actual)
{
    return CryptographicOperations.FixedTimeEquals(expected, actual);
}

Common Patterns

Authenticated Encryption

public class AuthenticatedMessage
{
    public byte[] Ciphertext { get; set; }
    public byte[] Mac { get; set; }

    public static AuthenticatedMessage Create(byte[] key, byte[] plaintext)
    {
        // Encrypt (using your preferred cipher)
        byte[] ciphertext = Encrypt(plaintext);

        // Authenticate
        using var kmac = KMac256.Create(key, customization: "Auth");
        Span<byte> mac = stackalloc byte[64];
        kmac.TryComputeHash(ciphertext, mac, out _);

        return new AuthenticatedMessage { Ciphertext = ciphertext, Mac = mac.ToArray() };
    }

    public bool Verify(byte[] key)
    {
        using var kmac = KMac256.Create(key, customization: "Auth");
        Span<byte> expectedMac = stackalloc byte[64];
        kmac.TryComputeHash(Ciphertext, expectedMac, out _);
        return CryptographicOperations.FixedTimeEquals(Mac, expectedMac);
    }
}

API Request Signing

public string SignRequest(byte[] key, string method, string path, string timestamp)
{
    string message = $"{method}:{path}:{timestamp}";
    byte[] messageBytes = Encoding.UTF8.GetBytes(message);

    using var blake3 = Blake3.CreateKeyed(key);
    Span<byte> signature = stackalloc byte[32];
    blake3.TryComputeHash(messageBytes, signature, out _);

    return Convert.ToBase64String(signature);
}

Session Token Generation

public byte[] GenerateSessionKey(byte[] masterKey, string userId, DateTime expiry)
{
    string context = $"session:{userId}:{expiry:O}";
    using var blake3 = Blake3.CreateDeriveKey(context);
    Span<byte> sessionKey = stackalloc byte[32];
    blake3.TryComputeHash(masterKey, sessionKey, out _);
    return sessionKey.ToArray();
}

See Also


© 2026 The Keepers of the CryptoHives