digest.hmac_sha256
Available inall subroutines.
Returns the HMAC-SHA256 of message using key, as a lowercase hexadecimal string with a 0x prefix.
Parameters
| Parameter | Type | Description |
|---|---|---|
key | STRING | The secret key for HMAC computation |
message | STRING | The message to authenticate |
The key is used directly as the HMAC key. For keys longer than 64 bytes (the SHA-256 block size), the key is first hashed with SHA-256 before use, as specified in RFC 2104.
The message can be any string, including an empty string. Multiple strings can be concatenated using the + operator before passing to the function.
Return value
Returns a 66-character string: a 0x prefix followed by 64 lowercase hexadecimal characters representing the 256-bit (32-byte) HMAC.
Example output: 0x095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67
If key is empty or not set, the function returns an empty string (not set).
Examples
Basic usage
declare local var.hmac STRING;set var.hmac = digest.hmac_sha256("secret-key", "hello world");# Result: 0x095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67To verify this output using OpenSSL:
$ echo -n "hello world" | openssl dgst -sha256 -hmac "secret-key"SHA2-256(stdin)= 095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67Signing a request
A common use case is signing API requests to prove authenticity:
sub vcl_recv { declare local var.string_to_sign STRING; declare local var.signature STRING; declare local var.secret_key STRING;
set var.secret_key = table.lookup(api_keys, "webhook-secret"); if (var.secret_key == "") { error 500 "Signing key not configured"; }
# Build the string to sign from request components set var.string_to_sign = req.method + "\n" + req.url.path + "\n" + req.http.Host + "\n" + now.sec;
set var.signature = digest.hmac_sha256(var.secret_key, var.string_to_sign);
# Add signature to request for backend verification set req.http.X-Signature = var.signature; set req.http.X-Timestamp = now.sec;}Verifying a webhook signature
When receiving webhooks, verify the signature matches:
sub vcl_recv { declare local var.expected_sig STRING; declare local var.secret_key STRING; declare local var.body STRING;
set var.secret_key = table.lookup(webhook_secrets, "github"); if (var.secret_key == "") { error 500 "Webhook secret not configured"; }
# Read the request body set var.body = req.body;
# Compute expected signature set var.expected_sig = digest.hmac_sha256(var.secret_key, var.body);
# Compare using constant-time comparison to prevent timing attacks if (!digest.secure_is_equal(var.expected_sig, req.http.X-Hub-Signature-256)) { error 401 "Invalid signature"; }}Using string concatenation
The message parameter accepts concatenated strings:
declare local var.hmac STRING;
# These produce the same resultset var.hmac = digest.hmac_sha256("key", "hello world");set var.hmac = digest.hmac_sha256("key", "hello" + " " + "world");Deriving keys for encryption
HMAC-SHA256 can be used to derive encryption keys from a master secret:
sub derive_key(STRING var.master_key, STRING var.context) STRING { return digest.hmac_sha256(var.master_key, "encryption-key|" + var.context);}
declare local var.encryption_key STRING;set var.encryption_key = derive_key(table.lookup(secrets, "master"), client.ip);Security considerations
Key management
- Store keys in edge dictionaries, not in VCL source code.
- Use keys of at least 32 bytes (256 bits) for security equivalent to SHA-256.
- Rotate keys periodically and have a process for key compromise.
Constant-time comparison
When comparing HMAC values for authentication, always use digest.secure_is_equal to prevent timing attacks. String comparison with == leaks information about which bytes matched, potentially allowing an attacker to forge valid authentication tags:
# WRONG - vulnerable to timing attacksif (var.computed_hmac == req.http.X-Signature) { ... }
# CORRECT - constant-time comparisonif (digest.secure_is_equal(var.computed_hmac, req.http.X-Signature)) { ... }Empty keys
If the key is empty or not set, the function returns an empty string. Always validate that key lookups succeed before computing an HMAC:
declare local var.key STRING;set var.key = table.lookup(secrets, "api-key");if (var.key == "") { error 500 "API key not found";}set var.hmac = digest.hmac_sha256(var.key, var.message);Related content
digest.hmac_sha256_base64- Returns Base64-encoded output instead of hex.digest.hmac_sha512- HMAC with SHA-512 for 512-bit output.digest.hmac_sha1- HMAC with SHA-1 (legacy, not recommended for new code).digest.secure_is_equal- Constant-time string comparison.crypto.encrypt_base64- Symmetric encryption using derived keys.