Definition and Overview
HMAC (Hash-based Message Authentication Code) is a specific type of message authentication code that uses a cryptographic hash function (such as SHA-256 or SHA-512) in combination with a secret key to produce a fixed-size authentication tag. The HMAC tag proves two properties simultaneously: that the message has not been tampered with (integrity) and that it was produced by someone possessing the secret key (authenticity). HMAC was formally defined in RFC 2104 by Bellare, Canetti, and Krawczyk in 1996.
The construction HMAC(K, m) = H((K' XOR opad) || H((K' XOR ipad) || m)) uses the hash function H in two nested applications with the key K padded and XORed with outer and inner padding constants. This double-hash construction is crucial -- simply prepending or appending the key to the message and hashing (i.e., H(K || m) or H(m || K)) is vulnerable to length extension attacks with Merkle-Damgard hash functions like SHA-256. The HMAC construction is provably secure against these attacks.
HMAC is ubiquitous in modern systems. JWT (JSON Web Tokens) use HMAC-SHA256 for signing. Webhook delivery services use HMAC signatures to prove that payloads originated from the claimed sender. API authentication schemes use HMAC to sign requests, preventing tampering and replay attacks. The Prismatic Platform uses HMAC for API request signing, webhook verification, and timed token generation across its OSINT tool integrations and API gateway.
Technical Deep Dive
HMAC Construction
HMAC-SHA256(key, message):
1. If key > block_size, key = SHA256(key)
2. If key < block_size, key = key || zeros
3. inner_key = key XOR 0x36363636... (ipad)
4. outer_key = key XOR 0x5C5C5C5C... (opad)
5. inner_hash = SHA256(inner_key || message)
6. hmac = SHA256(outer_key || inner_hash)
7. Return hmac (32 bytes for SHA-256)HMAC vs Other Authentication Methods
| Method | Integrity | Authenticity | Non-repudiation | Speed | Key Type |
|---|---|---|---|---|---|
| HMAC | Yes | Yes (shared secret) | No | Fast | Symmetric (shared) |
| Digital Signature (RSA/ECDSA) | Yes | Yes (public key) | Yes | Slow | Asymmetric (key pair) |
| Plain Hash | Yes | No | No | Fast | None |
| CRC/Checksum | Partial (error detection) | No | No | Very fast | None |
Common HMAC Variants
| Variant | Hash Function | Output Size | Security Level |
|---|---|---|---|
| HMAC-SHA256 | SHA-256 | 32 bytes | 128-bit |
| HMAC-SHA384 | SHA-384 | 48 bytes | 192-bit |
| HMAC-SHA512 | SHA-512 | 64 bytes | 256-bit |
| HMAC-SHA3-256 | SHA-3-256 | 32 bytes | 128-bit |
Security Requirements
| Requirement | Description | Violation Consequence |
|---|---|---|
| Key secrecy | Key must be known only to authorized parties | Full compromise -- anyone can forge |
| Key length | >= hash output size (32 bytes for SHA-256) | Reduced security margin |
| Key randomness | Key generated by CSPRNG | Predictable keys enable brute force |
| Constant-time comparison | HMAC verification must use constant-time comparison | Timing side-channel reveals HMAC byte-by-byte |
| Key rotation | Periodic key replacement | Limits exposure from key compromise |
Architecture and Implementation
HMAC-based authentication architecture consists of three components: key management (secure generation, storage, rotation, and distribution of shared secrets), signing (computing HMAC tags for outgoing messages or requests), and verification (validating HMAC tags on incoming messages using constant-time comparison).
The key management component is the most critical. Keys must be generated using a CSPRNG, stored securely (environment variables, secret managers, HSMs -- never in source code or configuration files), distributed through secure channels, and rotated periodically. The platform supports key rotation through a dual-key mechanism where both the current and previous keys are accepted during a transition period.
The verification component must use constant-time comparison (Plug.Crypto.secure_compare/2 in Elixir) to prevent timing side-channel attacks. A naive byte-by-byte comparison that returns early on mismatch leaks information about how many bytes of the HMAC are correct, enabling an attacker to incrementally construct a valid HMAC without knowing the key.
Usage in Prismatic Platform
The Prismatic Platform uses HMAC for API request signing, webhook verification from OSINT tool providers, and timed token authentication.
defmodule Prismatic.Security.HMAC do
@moduledoc """
HMAC-based message authentication for API requests,
webhook verification, and timed token generation.
Uses :crypto.mac/4 for HMAC computation and
Plug.Crypto.secure_compare/2 for constant-time verification.
"""
@type algorithm :: :sha256 | :sha384 | :sha512
@spec sign(binary(), binary(), algorithm()) :: binary()
def sign(message, secret_key, algorithm \\ :sha256) do
:crypto.mac(:hmac, algorithm, secret_key, message)
end
@spec sign_hex(binary(), binary(), algorithm()) :: String.t()
def sign_hex(message, secret_key, algorithm \\ :sha256) do
sign(message, secret_key, algorithm)
|> Base.encode16(case: :lower)
end
@spec verify(binary(), binary(), binary(), algorithm()) :: boolean()
def verify(message, signature, secret_key, algorithm \\ :sha256) do
expected = sign(message, secret_key, algorithm)
Plug.Crypto.secure_compare(expected, signature)
end
@spec verify_hex(binary(), String.t(), binary(), algorithm()) :: boolean()
def verify_hex(message, hex_signature, secret_key, algorithm \\ :sha256) do
case Base.decode16(hex_signature, case: :mixed) do
{:ok, signature} -> verify(message, signature, secret_key, algorithm)
:error -> false
end
end
@doc """
Signs an API request for authenticated access to
OSINT tool providers that use HMAC authentication.
"""
@spec sign_api_request(String.t(), String.t(), map(), binary()) :: map()
def sign_api_request(method, path, headers, secret_key) do
timestamp = System.system_time(:second) |> Integer.to_string()
string_to_sign = "#{}\n#{}\n#{}"
signature = sign_hex(string_to_sign, secret_key)
Map.merge(headers, %{
"X-Signature" => signature,
"X-Timestamp" => timestamp
})
end
@doc """
Verifies an incoming webhook payload from an OSINT
tool provider. Checks both signature validity and
timestamp freshness to prevent replay attacks.
"""
@spec verify_webhook(binary(), String.t(), String.t(), binary()) :: :ok | {:error, atom()}
def verify_webhook(payload, signature, timestamp, secret_key) do
max_age_seconds = 300
with :ok <- verify_timestamp_freshness(timestamp, max_age_seconds),
string_to_sign = "#{}.#{}",
true <- verify_hex(string_to_sign, signature, secret_key) do
:ok
else
{:error, reason} -> {:error, reason}
false -> {:error, :invalid_signature}
end
end
defp verify_timestamp_freshness(timestamp, max_age) do
case Integer.parse(timestamp) do
{ts, ""} ->
age = System.system_time(:second) - ts
if abs(age) <= max_age, do: :ok, else: {:error, :timestamp_expired}
_ ->
{:error, :invalid_timestamp}
end
end
endCross-References
- CSPRNG -- Secure key generation for HMAC
- CSRF -- Token-based protection complementing HMAC
- Gateway -- API gateway using HMAC authentication
- JWT -- Token format using HMAC signing
- Livebooks:
security_compliance/notebooks demonstrate HMAC operations - Academy: APISecurityAnalysis topic covers HMAC-based API authentication
See Also
Connect & Contribute
Created by Tomas Korcak (korczis) | Open Source under GHL