HMAC MAC
Hash-based Message Authentication Code
🎯 Overview
HMAC (Hash-based Message Authentication Code) is the industry-standard method for providing data integrity and authenticity verification using a cryptographic hash function combined with a secret key. It ensures that messages have not been tampered with and can only be verified by parties possessing the secret key.
✨ Key Features
Cryptographic Security
Security based on underlying hash function strength
Key-Dependent
Requires secret key for generation and verification
Industry Standard
RFC 2104 and FIPS 198-1 approved
High Performance
Fast computation and verification
Versatile
Works with any cryptographic hash function
Widely Adopted
Used in TLS, IPsec, JWT, and many protocols
🎯 Common Use Cases
🔌 API Authentication
Secure API request signing with HMAC-based authentication tokens
🛡️ Message Integrity
Verify messages haven't been tampered with during transmission
🔒 TLS/SSL Protocols
Handshake authentication and record protection in secure connections
🎫 JWT Tokens
JSON Web Token signing for secure authentication systems
🛡️ IPsec VPNs
Packet authentication in VPN protocols for secure networking
🗃️ Database Integrity
Verify database record authenticity and detect tampering
🔧 Algorithm Parameters
Hash Function
Options: SHA-256, SHA-512, BLAKE2b, BLAKE3
Recommendation: SHA-256 for general use, BLAKE3 for performance
Key Size
Minimum: Hash output size
Recommended: 32-64 bytes
Maximum: Unlimited (hashed if > block size)
Tag Size
Full: Hash output size
Minimum Secure: 128 bits (16 bytes)
Common: 256 bits (32 bytes)
Block Size
SHA-256: 64 bytes
SHA-512: 128 bytes
BLAKE2b: 128 bytes
🔒 Security Properties
Unforgeability
Cannot create valid HMAC without secret key
Collision Resistance
Inherits security from underlying hash function
Key Recovery Resistance
Computationally infeasible to recover secret key
Pseudorandomness
Output appears random to attackers without key
Length Extension Immunity
Immune to length extension attacks
Timing Attack Resistance
Constant-time verification recommended
💻 Implementation
HMAC can be implemented with any cryptographic hash function. Choose the appropriate hash based on your security requirements and performance needs.
Basic HMAC Usage
API Authentication
⚠️ Security Guidelines
🚨 Critical Security Requirements
⚡ Performance Analysis
Hash Function Performance Comparison
HMAC-SHA256
HMAC-SHA512
HMAC-BLAKE2b
HMAC-BLAKE3
🚀 Real-World Applications
REST API Security
HMAC-SHA256 for request signing in OAuth, AWS Signature v4, and custom API authentication
JWT Token Signing
HS256/HS512 algorithms for JSON Web Token integrity in authentication systems
TLS Record Protection
HMAC in TLS 1.2 and earlier for record authentication and integrity verification
IPsec Authentication
ESP and AH protocols use HMAC for packet authentication in VPN connections
File Integrity Monitoring
Database record protection and backup verification systems
Message Queue Security
Apache Kafka, RabbitMQ message authentication and producer verification
⚖️ HMAC vs Alternative MACs
HMAC
Poly1305
SipHash
Digital Signatures
🔗 Related Algorithms
📋 Standards & References
🏛️ RFC 2104
HMAC: Keyed-Hashing for Message Authentication - Original HMAC specification
🏛️ FIPS 198-1
The Keyed-Hash Message Authentication Code - NIST federal standard
🌐 RFC 7518
JSON Web Algorithms (JWA) - HMAC in JWT specifications
📊 RFC 4231
HMAC test vectors for SHA-224, SHA-256, SHA-384, and SHA-512
def __init__(self, secret: bytes):
self.hmac = HMAC(secret, SHA256())
def create_token(self, payload: dict, expires_in: int = 3600) -> str:
"""Create JWT token with HMAC signature"""
import time
# Header
header = {
"alg": "HS256",
"typ": "JWT"
}
# Add expiration to payload
payload["exp"] = int(time.time()) + expires_in
payload["iat"] = int(time.time())
# Encode header and payload
header_b64 = self.base64url_encode(json.dumps(header).encode())
payload_b64 = self.base64url_encode(json.dumps(payload).encode())
# Create signature
message = header_b64 + b'.' + payload_b64
signature = self.hmac.compute(message)
signature_b64 = self.base64url_encode(signature)
# Combine token
return (header_b64 + b'.' + payload_b64 + b'.' + signature_b64).decode()
def verify_token(self, token: str) -> dict:
"""Verify and decode JWT token"""
import time
parts = token.encode().split(b'.')
if len(parts) != 3:
raise ValueError("Invalid JWT format")
header_b64, payload_b64, signature_b64 = parts
# Verify signature
message = header_b64 + b'.' + payload_b64
expected_signature = self.hmac.compute(message)
provided_signature = self.base64url_decode(signature_b64)
if not self.constant_time_compare(expected_signature, provided_signature):
raise ValueError("Invalid JWT signature")
# Decode payload
payload = json.loads(self.base64url_decode(payload_b64))
# Check expiration
if payload.get("exp", 0) < time.time():
raise ValueError("JWT token expired")
return payload
def base64url_encode(self, data: bytes) -> bytes:
"""Base64URL encoding without padding"""
return base64.urlsafe_b64encode(data).rstrip(b'=')
def base64url_decode(self, data: bytes) -> bytes:
"""Base64URL decoding with padding"""
padding = 4 - (len(data) % 4)
if padding != 4:
data += b'=' * padding
return base64.urlsafe_b64decode(data)
File integrity verification
class HMACFileIntegrity: def init(self, master_key: bytes): self.master_key = master_key self.hmac = HMAC(master_key, SHA256())
def compute_file_hmac(self, filepath: str, chunk_size: int = 8192) -> bytes:
"""Compute HMAC for large file"""
hmac = HMAC(self.master_key, SHA256())
with open(filepath, 'rb') as f:
while chunk := f.read(chunk_size):
hmac.update(chunk)
return hmac.finalize()
def create_manifest(self, file_paths: list) -> dict:
"""Create integrity manifest for multiple files"""
manifest = {
'version': '1.0',
'algorithm': 'HMAC-SHA256',
'files': {}
}
for filepath in file_paths:
file_hmac = self.compute_file_hmac(filepath)
file_size = os.path.getsize(filepath)
manifest['files'][filepath] = {
'hmac': file_hmac.hex(),
'size': file_size
}
return manifest
def verify_manifest(self, manifest: dict) -> dict:
"""Verify files against manifest"""
results = {
'valid': [],
'invalid': [],
'missing': []
}
for filepath, file_info in manifest['files'].items():
if not os.path.exists(filepath):
results['missing'].append(filepath)
continue
# Verify size
if os.path.getsize(filepath) != file_info['size']:
results['invalid'].append(filepath)
continue
# Verify HMAC
computed_hmac = self.compute_file_hmac(filepath)
expected_hmac = bytes.fromhex(file_info['hmac'])
if computed_hmac == expected_hmac:
results['valid'].append(filepath)
else:
results['invalid'].append(filepath)
return results
Database record authentication
class HMACDatabaseAuth: def init(self, table_key: bytes): self.table_key = table_key self.hmac = HMAC(table_key, SHA256())
def authenticate_record(self, record_data: dict) -> str:
"""Create HMAC for database record"""
# Create canonical representation
canonical = json.dumps(record_data, sort_keys=True, separators=(',', ':'))
# Compute HMAC
tag = self.hmac.compute(canonical.encode())
return tag.hex()
def verify_record(self, record_data: dict, stored_hmac: str) -> bool:
"""Verify database record authenticity"""
computed_hmac = self.authenticate_record(record_data)
return self.constant_time_compare(
bytes.fromhex(computed_hmac),
bytes.fromhex(stored_hmac)
)
def batch_authenticate(self, records: list) -> list:
"""Authenticate multiple records efficiently"""
results = []
for record in records:
hmac_tag = self.authenticate_record(record)
results.append({
'record': record,
'hmac': hmac_tag
})
return results
Network protocol authentication
class HMACProtocol: def init(self, shared_secret: bytes): self.shared_secret = shared_secret self.sequence_number = 0
def create_authenticated_message(self, message: bytes) -> bytes:
"""Create authenticated protocol message"""
# Message header
header = struct.pack('>HI',
0x0100, # Protocol version
self.sequence_number)
# Compute HMAC over header + message
hmac = HMAC(self.shared_secret, SHA256())
hmac.update(header)
hmac.update(message)
tag = hmac.finalize()
self.sequence_number += 1
# Return: header + message + tag
return header + message + tag
def verify_authenticated_message(self, packet: bytes) -> tuple:
"""Verify and extract authenticated message"""
if len(packet) < 38: # 6 bytes header + 32 bytes tag minimum
raise ValueError("Packet too short")
# Parse packet
header = packet[:6]
message = packet[6:-32]
tag = packet[-32:]
# Extract sequence number
version, seq_num = struct.unpack('>HI', header)
if version != 0x0100:
raise ValueError("Invalid protocol version")
# Verify HMAC
hmac = HMAC(self.shared_secret, SHA256())
hmac.update(header)
hmac.update(message)
expected_tag = hmac.finalize()
if not self.constant_time_compare(tag, expected_tag):
raise ValueError("HMAC verification failed")
return message, seq_num ```
Implementation Details
# HMAC algorithm implementation (simplified)
class HMACCore:
def __init__(self, key: bytes, hash_function):
self.hash_function = hash_function
self.block_size = hash_function.block_size
self.output_size = hash_function.digest_size
# Process key
if len(key) > self.block_size:
# Hash long keys
key = hash_function.hash(key)
# Pad key to block size
if len(key) < self.block_size:
key = key + b'\x00' * (self.block_size - len(key))
# Create inner and outer padding
self.inner_key = bytes(k ^ 0x36 for k in key)
self.outer_key = bytes(k ^ 0x5c for k in key)
def compute(self, message: bytes) -> bytes:
"""Compute HMAC"""
# Inner hash: H(K ⊕ ipad || message)
inner_hasher = self.hash_function()
inner_hasher.update(self.inner_key)
inner_hasher.update(message)
inner_hash = inner_hasher.finalize()
# Outer hash: H(K ⊕ opad || inner_hash)
outer_hasher = self.hash_function()
outer_hasher.update(self.outer_key)
outer_hasher.update(inner_hash)
return outer_hasher.finalize()
def verify(self, message: bytes, tag: bytes) -> bool:
"""Verify HMAC tag"""
computed_tag = self.compute(message)
return self.constant_time_compare(computed_tag, tag)
Security Considerations
Best Practices
- Key Management
- Use keys at least as long as hash output
- Generate keys using cryptographically secure random sources
- Rotate keys regularly
- Store keys securely
- Hash Function Selection
- Use SHA-256 or stronger for new applications
- Avoid SHA-1 for security-critical applications
- Consider BLAKE2b or BLAKE3 for performance
- Ensure hash function is collision-resistant
- Tag Length
- Use full hash output for maximum security
- Minimum 128 bits for truncated tags
- Consider bandwidth vs security trade-offs
- Document truncation decisions
- Implementation Security
- Use constant-time comparison for verification
- Clear sensitive data from memory
- Protect against side-channel attacks
- Validate all inputs
Common Pitfalls
# DON'T: Use weak keys
weak_key = b"password" # Too short and predictable
# WRONG: Weak key compromises security
hmac = HMAC(weak_key, SHA256())
# DO: Use strong, random keys
strong_key = os.urandom(32) # 256 bits of randomness
hmac = HMAC(strong_key, SHA256())
# DON'T: Use timing-vulnerable comparison
def bad_verify(tag1, tag2):
# WRONG: Early return leaks timing information
for i in range(len(tag1)):
if tag1[i] != tag2[i]:
return False
return True
# DO: Use constant-time comparison
def secure_verify(tag1, tag2):
if len(tag1) != len(tag2):
return False
result = 0
for a, b in zip(tag1, tag2):
result |= a ^ b
return result == 0
# DON'T: Truncate tags too aggressively
tag = hmac.compute(message)
short_tag = tag[:4] # WRONG: Only 32 bits, easily brute-forced
# DO: Use adequate tag length
tag = hmac.compute(message)
secure_tag = tag[:16] # 128 bits minimum for security
# DON'T: Reuse keys across different contexts
api_hmac = HMAC(shared_key, SHA256())
file_hmac = HMAC(shared_key, SHA256()) # WRONG: Same key for different purposes
# DO: Derive separate keys for different purposes
from metamui_crypto import HKDF
hkdf = HKDF(SHA256())
api_key = hkdf.derive(shared_key, b"api_auth", 32)
file_key = hkdf.derive(shared_key, b"file_integrity", 32)
Performance Characteristics
Benchmarks
| Hash Function | Message Size | Throughput | Time | Relative Speed |
|---|---|---|---|---|
| HMAC-SHA256 | 1 KB | 245 MB/s | 4.1 μs | 1.00x |
| HMAC-SHA512 | 1 KB | 198 MB/s | 5.1 μs | 0.81x |
| HMAC-BLAKE2b | 1 KB | 892 MB/s | 1.1 μs | 3.64x |
| HMAC-BLAKE3 | 1 KB | 1.2 GB/s | 0.83 μs | 4.90x |
Performance Optimization
# Batch HMAC computation
class BatchHMAC:
def __init__(self, key: bytes, hash_function):
self.hmac = HMAC(key, hash_function)
def compute_batch(self, messages: list) -> list:
"""Compute HMAC for multiple messages efficiently"""
results = []
for message in messages:
tag = self.hmac.compute(message)
results.append(tag)
return results
# Streaming HMAC for large data
def stream_hmac(key: bytes, data_stream, hash_function):
"""Compute HMAC for streaming data"""
hmac = HMAC(key, hash_function)
for chunk in data_stream:
hmac.update(chunk)
return hmac.finalize()
# Pre-computed HMAC for repeated operations
class PrecomputedHMAC:
def __init__(self, key: bytes, hash_function):
self.hash_function = hash_function
# Pre-compute key processing
block_size = hash_function.block_size
if len(key) > block_size:
key = hash_function.hash(key)
if len(key) < block_size:
key = key + b'\x00' * (block_size - len(key))
self.inner_key = bytes(k ^ 0x36 for k in key)
self.outer_key = bytes(k ^ 0x5c for k in key)
def compute(self, message: bytes) -> bytes:
"""Fast HMAC computation with pre-computed keys"""
# Inner hash
inner = self.hash_function()
inner.update(self.inner_key)
inner.update(message)
inner_result = inner.finalize()
# Outer hash
outer = self.hash_function()
outer.update(self.outer_key)
outer.update(inner_result)
return outer.finalize()
Use Cases
1. Secure API Authentication
class SecureAPIClient:
def __init__(self, api_key: str, api_secret: str):
self.api_key = api_key
self.api_secret = api_secret.encode()
def make_request(self, method: str, endpoint: str,
data: dict = None) -> dict:
"""Make authenticated API request"""
import time
import requests
# Prepare request
timestamp = str(int(time.time()))
body = json.dumps(data) if data else ""
# Create signature
message = f"{method}\n{endpoint}\n{timestamp}\n{body}"
hmac = HMAC(self.api_secret, SHA256())
signature = hmac.compute(message.encode()).hex()
# Headers
headers = {
'X-API-Key': self.api_key,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json'
}
# Make request
response = requests.request(
method=method,
url=f"https://api.example.com{endpoint}",
headers=headers,
data=body if body else None
)
return response.json()
2. Message Queue Authentication
class HMACMessageQueue:
def __init__(self, queue_key: bytes):
self.queue_key = queue_key
self.hmac = HMAC(queue_key, SHA256())
def publish_message(self, topic: str, message: dict) -> dict:
"""Publish authenticated message to queue"""
import time
# Create message envelope
envelope = {
'topic': topic,
'timestamp': time.time(),
'message_id': os.urandom(16).hex(),
'payload': message
}
# Serialize and sign
serialized = json.dumps(envelope, sort_keys=True)
signature = self.hmac.compute(serialized.encode())
return {
'envelope': envelope,
'signature': signature.hex()
}
def verify_message(self, signed_message: dict) -> dict:
"""Verify and extract message"""
envelope = signed_message['envelope']
signature = bytes.fromhex(signed_message['signature'])
# Verify signature
serialized = json.dumps(envelope, sort_keys=True)
expected_signature = self.hmac.compute(serialized.encode())
if not self.constant_time_compare(signature, expected_signature):
raise ValueError("Message authentication failed")
return envelope['payload']
3. Configuration File Protection
class HMACConfigProtection:
def __init__(self, config_key: bytes):
self.config_key = config_key
self.hmac = HMAC(config_key, SHA256())
def save_config(self, config: dict, filepath: str):
"""Save configuration with HMAC protection"""
# Serialize configuration
config_json = json.dumps(config, sort_keys=True, indent=2)
# Compute HMAC
hmac_tag = self.hmac.compute(config_json.encode())
# Create protected configuration
protected_config = {
'version': '1.0',
'config': config,
'hmac': hmac_tag.hex()
}
# Save to file
with open(filepath, 'w') as f:
json.dump(protected_config, f, indent=2)
def load_config(self, filepath: str) -> dict:
"""Load and verify configuration"""
# Load from file
with open(filepath, 'r') as f:
protected_config = json.load(f)
# Extract components
config = protected_config['config']
stored_hmac = bytes.fromhex(protected_config['hmac'])
# Verify HMAC
config_json = json.dumps(config, sort_keys=True, indent=2)
computed_hmac = self.hmac.compute(config_json.encode())
if not self.constant_time_compare(stored_hmac, computed_hmac):
raise ValueError("Configuration integrity check failed")
return config
Comparison with Other MACs
HMAC vs Poly1305
| Feature | HMAC | Poly1305 |
|---|---|---|
| Key Reuse | Yes | No (one-time) |
| Speed | Moderate | Very Fast |
| Security Basis | Hash function | Information-theoretic |
| Tag Size | Variable | 16 bytes |
| Use Case | General purpose | Stream ciphers |
HMAC vs CMAC
| Feature | HMAC | CMAC |
|---|---|---|
| Primitive | Hash function | Block cipher |
| Key Size | Flexible | Block cipher key size |
| Performance | Hash-dependent | Cipher-dependent |
| Standardization | RFC 2104 | NIST SP 800-38B |
When to Use HMAC
# Use HMAC for general authentication
if need_general_purpose_mac:
mac = HMAC(key, SHA256())
# Use HMAC for API authentication
if building_api_authentication:
mac = HMAC(api_secret, SHA256())
# Use HMAC for file integrity
if need_file_integrity_check:
mac = HMAC(file_key, SHA256())
# Use HMAC for protocol authentication
if implementing_secure_protocol:
mac = HMAC(protocol_key, SHA256())
Migration Guide
From Custom MAC to HMAC
# Before: Custom MAC (insecure)
def custom_mac(key, message):
# WRONG: Simple concatenation is vulnerable
return hashlib.sha256(key + message).digest()
# After: HMAC (secure)
from metamui_crypto import HMAC, SHA256
hmac = HMAC(key, SHA256())
tag = hmac.compute(message)
From MD5-based MAC to HMAC
# Before: MD5-based MAC (weak)
import hashlib
def md5_mac(key, message):
return hashlib.md5(key + message).digest()
# After: HMAC with strong hash function
hmac = HMAC(key, SHA256())
tag = hmac.compute(message)
Test Vectors
RFC 2104 Test Vectors
# Test Case 1
key = b'\x0b' * 20
message = b"Hi There"
hmac = HMAC(key, SHA256())
tag = hmac.compute(message)
assert tag.hex() == "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
# Test Case 2
key = b"Jefe"
message = b"what do ya want for nothing?"
hmac = HMAC(key, SHA256())
tag = hmac.compute(message)
assert tag.hex() == "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
# Test Case 3
key = b'\xaa' * 20
message = b'\xdd' * 50
hmac = HMAC(key, SHA256())
tag = hmac.compute(message)
assert tag.hex() == "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
Incremental Computation Test
# Test incremental vs one-shot computation
key = os.urandom(32)
message = b"The quick brown fox jumps over the lazy dog"
# One-shot
hmac1 = HMAC(key, SHA256())
tag1 = hmac1.compute(message)
# Incremental
hmac2 = HMAC(key, SHA256())
hmac2.update(b"The quick brown ")
hmac2.update(b"fox jumps over ")
hmac2.update(b"the lazy dog")
tag2 = hmac2.finalize()
assert tag1 == tag2
Related Algorithms
- Poly1305 - High-speed one-time authenticator
- SipHash - Fast short-input PRF
- HMAC-DRBG - HMAC-based random number generator
- SHA-256 - Common hash function for HMAC
- Blake3 - Fast alternative hash function
References
- RFC 2104 - HMAC: Keyed-Hashing for Message Authentication
- FIPS 198-1 - The Keyed-Hash Message Authentication Code
- RFC 4231 - Identifiers and Test Vectors for HMAC-SHA-224, HMAC-SHA-256, HMAC-SHA-384, and HMAC-SHA-512
- NIST SP 800-107 - Recommendation for Applications Using Approved Hash Algorithms