---
title: digest.hmac_sha256
summary: null
url: >-
  https://www.fastly.com/documentation/reference/vcl/functions/cryptographic/digest-hmac-sha256
---

```
STRING digest.hmac_sha256(STRING key, STRING s)
```

**Available in:** all 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

```vcl
declare local var.hmac STRING;
set var.hmac = digest.hmac_sha256("secret-key", "hello world");
# Result: 0x095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67
```

To verify this output using OpenSSL:

```term
$ echo -n "hello world" | openssl dgst -sha256 -hmac "secret-key"
SHA2-256(stdin)= 095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67
```

### Signing a request

A common use case is signing API requests to prove authenticity:

```vcl
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:

```vcl
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:

```vcl
declare local var.hmac STRING;

# These produce the same result
set 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:

```vcl
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:

```vcl
# WRONG - vulnerable to timing attacks
if (var.computed_hmac == req.http.X-Signature) { ... }

# CORRECT - constant-time comparison
if (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:

```vcl
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.
