---
title: digest.rsa_verify
summary: null
url: >-
  https://www.fastly.com/documentation/reference/vcl/functions/cryptographic/digest-rsa-verify
---

```
BOOL digest.rsa_verify(ID hash_method, STRING public_key, STRING payload, STRING digest, ID base64_variant?)
```

**Available in:** all subroutines

Returns `true` if the RSA signature of `payload` using `public_key` matches
`signature`. Uses the PKCS#1 v1.5 signature scheme.

## Parameters

| Parameter        | Type   | Description                                                                                            |
| ---------------- | ------ | ------------------------------------------------------------------------------------------------------ |
| `hash_method`    | ID     | Hash algorithm: `sha256`, `sha384`, `sha512`, `sha1`, or `default` (same as `sha256`)                  |
| `public_key`     | STRING | RSA public key in PEM format                                                                           |
| `payload`        | STRING | The message that was signed (raw string, not encoded)                                                  |
| `signature`      | STRING | Base64-encoded signature to verify                                                                     |
| `base64_variant` | ID     | Optional. Base64 variant for decoding `signature`: `standard`, `url`, `url_nopad` (default), `default` |

The `payload` parameter is the raw message to verify, not a Base64 or hex encoding of it.

Since VCL strings cannot contain NUL bytes, binary payloads are not supported. If you need to verify a signature over binary data, you must use a text-safe encoding on both the signing and verification sides.

## Signature scheme

This function uses the RSASSA-PKCS1-v1_5 signature scheme as defined in [RFC 8017](https://datatracker.ietf.org/doc/html/rfc8017#section-8.2). This is the signature scheme used by:

- RS256, RS384, RS512 algorithms in JWTs ([RFC 7518 Section 3.3](https://datatracker.ietf.org/doc/html/rfc7518#section-3.3)).
- OpenSSL's `openssl dgst -sign` command with RSA keys.

RSA-PSS signatures (PS256, PS384, PS512) are not supported by this function.

## Key sizes

Any standard RSA key size is supported. Common sizes include:

| Key size | Security level                           |
| -------- | ---------------------------------------- |
| 2048-bit | Minimum recommended for new applications |
| 3072-bit | Provides ~128-bit security               |
| 4096-bit | Provides ~140-bit security               |

Keys smaller than 2048 bits are deprecated and should not be used for new applications, though they will still verify successfully.

## Base64 variants

The `base64_variant` parameter controls how the signature is decoded:

| Variant     | Alphabet            | Padding  | RFC                |
| ----------- | ------------------- | -------- | ------------------ |
| `standard`  | `A-Za-z0-9+/`       | Required | RFC 4648 Section 4 |
| `url`       | `A-Za-z0-9-_`       | Required | RFC 4648 Section 5 |
| `url_nopad` | `A-Za-z0-9-_`       | Optional | RFC 4648 Section 5 |
| `default`   | Same as `url_nopad` | Optional | —                  |

The default is `url_nopad`, which is appropriate for JWT signatures.

## Examples

### Verifying a signature

```vcl
declare local var.verified BOOL;

set var.verified = digest.rsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpb4ATDPigeVvSfmxqIe
z0LGD87v4Qzu5nWMlknPlS9pBkz+++MkBZwJFjXLwmqB0KvdABo3fErO5YkgO7UC
ayLXLLk2WBoQ4IvS+L+P+yheR8D+0AJGpNzy9R8kC99RlCdcL37BczHUfaT6WZjs
iFvR9SP2emAY4sk+Rcyu7EPsyoa7JCYSDhYGE8ljPB1MLQLGFpJQDnpOD/SSqfz9
7PNWpLI6OGNqX4u+6fexrvbRw31ps7FUyXtiq3oDWKBwsca9cj31n0vfjmQdle+1
LDhrMxbKuOzgf25c6fkRgTU9GgxiwuMqsjfQICj5y2wdigjU2kE+Wz1a/hkzz6t1
KwIDAQAB
-----END PUBLIC KEY-----"},
    "hello world",
    "Q5HqtEqxfbKrhBaJ17b0RuAigriAhGG0iKnzlxlZdEtdlFEMEgkqhjXq-WwoVoNcFuUi_X3YBpoLo7Emghy-ZcaLRLXziUChYe5eMMwhHde1olFROruBiOe3xHOF9rnw6yQEIQ-8O70OkC4jDSUSoEAMpLTo_eIHjcT7k2VE49XF4nyotzyo8vpUQ7W5GUwHFV7Lu_TQQeTsMWDdE7AsWwc2_ULGf5p1Vf_Ihz3BDiNNrWbMjUgkS373m9C3PvCagg-nIQJ8h1lR4QAWxIriRiHAb-xbyXH1SAxCW5FozoinoObJYpQj-rYKid2rntjghpkgSYi_DBoRsxcfahop0w",
    url_nopad);

if (var.verified) {
    set req.http.X-Verified = "true";
} else {
    error 403 "Invalid signature";
}
```

To generate your own test vectors, use OpenSSL:

```term
$ openssl genrsa -out private.pem 2048
$ openssl rsa -in private.pem -pubout -out public.pem
$ echo -n "hello world" | openssl dgst -sha256 -sign private.pem | openssl base64 -A | tr '+/' '-_' | tr -d '='
```

### Verifying a JWT RS256 signature

A JWT consists of three Base64URL-encoded parts separated by dots: `header.payload.signature`. The RS256 signature is computed over the ASCII bytes of the header and payload parts joined by a dot.

This example verifies a JWT signed with the RS256 algorithm:

```vcl
declare local var.jwt STRING;
declare local var.header_payload STRING;
declare local var.signature STRING;
declare local var.verified BOOL;

# Test JWT signed with the RS256 algorithm
set var.jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.jWE265FCZzjhybC8Cy-cMe2YdzzBYrbhHDp9cSocle_jS90ZvRYANwqyl3mUr7PLLXTRDDHy9wCULMuHaw2U_rCJfSXlaqMP8EOHRfAAkPmyKI2GQ9RGiq_JaBGd8QBRNaZgQZdM961J4KcSnRVfgLaznykvUS7fxNq64157hM6d8n29P6_nV3uJkz-viywD6YlDWTuL_It4nDXgQhSS4Xk7FzuDdBKFQo9l8Z2HDVOYM15VQ43lcXCgHMaGMH_1FCLLCP1hpu4C1a9tQLZ8LhVOrkS_KVhbYijQA0NRrxd40MEQwQGoDiExSa_tkJ9wRum53AvBf0Mvl5Y5vd_szQ";

# Extract header.payload and signature from the JWT
if (var.jwt ~ "^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$") {
    set var.header_payload = re.group.1;
    set var.signature = re.group.2;
} else {
    error 401 "Malformed JWT";
}

# Verify the signature
set var.verified = digest.rsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqaBIzfdfRoyMA5iFbGOp
JWwSezwkz9CmjL2dE/46kQFvj9Mr4R5OZDpqUEGLoTC52z8IuhOESMBr8L9F+KTd
xZN00VHzjVvYUvVyV7xgTV3yhroOAewLlAW8ZwlBtinTEATIqEkCxyhaNfktyJzX
SMgS25eZltEsGVavIzwxLqA18eaCmMReB9BwFh2j0bUo/hLBD8AGZIREbt7e1E5x
nbjBZb+On2Qiq1bUYR/SsyXvFIOo73f/712LZ97P1n6lGAj5LTq4EDVHVJDK9QAG
lBP6EU2+6fBBlm2yNhsnjDELZEMDWFUQNibdjNcsHy5tK7X5+8PgPhFurPiO9owc
jwIDAQAB
-----END PUBLIC KEY-----"},
    var.header_payload,
    var.signature,
    url_nopad);

if (var.verified) {
    set req.http.X-JWT-Valid = "true";
} else {
    error 401 "Invalid JWT signature";
}
```

Decoded, the JWT parts are:

- Header: `{"alg":"RS256","typ":"JWT"}`.
- Payload: `{"sub":"1234567890","name":"John Doe","admin":true,"iat":1516239022}`.
- Signature: 256 bytes (for a 2048-bit RSA key).

### Verifying a JWT from an Authorization header

When verifying JWTs from an `Authorization: Bearer` header:

```vcl
declare local var.header_payload STRING;
declare local var.signature STRING;

if (req.http.Authorization ~ "^Bearer ([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$") {
    set var.header_payload = re.group.1;
    set var.signature = re.group.2;
} else {
    error 401 "Invalid Authorization header";
}

if (!digest.rsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqaBIzfdfRoyMA5iFbGOp
JWwSezwkz9CmjL2dE/46kQFvj9Mr4R5OZDpqUEGLoTC52z8IuhOESMBr8L9F+KTd
xZN00VHzjVvYUvVyV7xgTV3yhroOAewLlAW8ZwlBtinTEATIqEkCxyhaNfktyJzX
SMgS25eZltEsGVavIzwxLqA18eaCmMReB9BwFh2j0bUo/hLBD8AGZIREbt7e1E5x
nbjBZb+On2Qiq1bUYR/SsyXvFIOo73f/712LZ97P1n6lGAj5LTq4EDVHVJDK9QAG
lBP6EU2+6fBBlm2yNhsnjDELZEMDWFUQNibdjNcsHy5tK7X5+8PgPhFurPiO9owc
jwIDAQAB
-----END PUBLIC KEY-----"},
    var.header_payload,
    var.signature,
    url_nopad))
{
    error 401 "Invalid JWT signature";
}
```

This example only verifies the cryptographic signature. You should also decode and validate the claims (expiration time, issuer, audience, etc.).

### Using a public key from an edge dictionary

Public keys can be loaded from an edge dictionary at runtime. Always verify that the lookup succeeded before using the key:

```vcl
declare local var.public_key STRING;
set var.public_key = table.lookup(rs256_keys, "my-service");

if (var.public_key == "") {
    error 500 "Signing key not configured";
}

if (digest.rsa_verify(sha256, var.public_key, req.http.Message, req.http.Sig, url_nopad)) {
    set req.http.X-Signature-Valid = "true";
} else {
    error 403 "Invalid signature";
}
```

When the key is a string literal, it is validated at compile time (invalid keys cause a compilation error) and parsed once at VCL load time. When the key is provided at runtime from a variable, it is parsed on every request and invalid keys cause the function to return `false`.

### Using different hash algorithms

The RS256, RS384, and RS512 JWT algorithms correspond to `sha256`, `sha384`, and `sha512`:

```vcl
# RS256 - SHA-256 (most common)
set var.verified = digest.rsa_verify(sha256, var.public_key, var.message, var.signature, url_nopad);

# RS384 - SHA-384
set var.verified = digest.rsa_verify(sha384, var.public_key, var.message, var.signature, url_nopad);

# RS512 - SHA-512
set var.verified = digest.rsa_verify(sha512, var.public_key, var.message, var.signature, url_nopad);
```

The `sha1` algorithm is also supported for legacy compatibility but should not be used for new applications due to known weaknesses in SHA-1.

### Using standard Base64 encoding

If your signing system produces standard Base64 (with `+/` and `=` padding) instead of Base64URL, use the `standard` variant:

```vcl
declare local var.verified BOOL;

set var.verified = digest.rsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpb4ATDPigeVvSfmxqIe
z0LGD87v4Qzu5nWMlknPlS9pBkz+++MkBZwJFjXLwmqB0KvdABo3fErO5YkgO7UC
ayLXLLk2WBoQ4IvS+L+P+yheR8D+0AJGpNzy9R8kC99RlCdcL37BczHUfaT6WZjs
iFvR9SP2emAY4sk+Rcyu7EPsyoa7JCYSDhYGE8ljPB1MLQLGFpJQDnpOD/SSqfz9
7PNWpLI6OGNqX4u+6fexrvbRw31ps7FUyXtiq3oDWKBwsca9cj31n0vfjmQdle+1
LDhrMxbKuOzgf25c6fkRgTU9GgxiwuMqsjfQICj5y2wdigjU2kE+Wz1a/hkzz6t1
KwIDAQAB
-----END PUBLIC KEY-----"},
    "hello world",
    "Q5HqtEqxfbKrhBaJ17b0RuAigriAhGG0iKnzlxlZdEtdlFEMEgkqhjXq+WwoVoNcFuUi/X3YBpoLo7Emghy+ZcaLRLXziUChYe5eMMwhHde1olFROruBiOe3xHOF9rnw6yQEIQ+8O70OkC4jDSUSoEAMpLTo/eIHjcT7k2VE49XF4nyotzyo8vpUQ7W5GUwHFV7Lu/TQQeTsMWDdE7AsWwc2/ULGf5p1Vf/Ihz3BDiNNrWbMjUgkS373m9C3PvCagg+nIQJ8h1lR4QAWxIriRiHAb+xbyXH1SAxCW5FozoinoObJYpQj+rYKid2rntjghpkgSYi/DBoRsxcfahop0w==",
    standard);
```

## Security considerations

### Algorithm confusion

Each signing key should be used with exactly one algorithm. Store RS256 keys separately from keys intended for other algorithms (ES256, HS256, etc.), and never select the algorithm based on untrusted input.

The `alg` header in a JWT is not signed and can be modified by an attacker. Never use it to select which verification function to call. Instead, determine the expected algorithm from context (the token type, the issuer, or the key identifier).

You can use the `alg` header as an early rejection path: if you expect RS256 and the token claims a different algorithm, reject it immediately without performing cryptographic verification:

```vcl
declare local var.public_key STRING;
declare local var.header STRING;
declare local var.header_payload STRING;
declare local var.signature STRING;

# Look up the RS256 key for this service - only RS256 keys are stored here
set var.public_key = table.lookup(rs256_keys, "auth-service");
if (var.public_key == "") {
    error 500 "RS256 signing key not found";
}

# Parse the JWT into header, header.payload, and signature
if (req.http.Authorization ~ "^Bearer (([a-zA-Z0-9_-]+)\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$") {
    set var.header_payload = re.group.1;
    set var.header = re.group.2;
    set var.signature = re.group.3;
} else {
    error 401 "Invalid Authorization header";
}

# Early rejection: if the token claims a different algorithm, reject without crypto
if (digest.base64url_nopad_decode(var.header) !~ {""alg"\s*:\s*"RS256"}) {
    error 401 "Unsupported algorithm";
}

# Verify with RS256
if (!digest.rsa_verify(sha256, var.public_key, var.header_payload, var.signature, url_nopad)) {
    error 401 "Invalid signature";
}
```

### RSA-PSS

This function only supports PKCS#1 v1.5 signatures (RS256/RS384/RS512). If your signing system uses RSA-PSS (PS256/PS384/PS512), those signatures cannot be verified with this function.

## Errors

When the public key is a string literal, an invalid or malformed key causes a compile-time error. Otherwise, all error conditions cause the function to silently return `false`:

- Any parameter is not set (e.g., a header that was never received).
- Invalid or malformed public key (when loaded at runtime).
- Key is not an RSA public key (e.g., an ECDSA key was provided).
- Invalid Base64 encoding in the signature.
- Signature does not match the payload.

This function does not set `fastly.error`.

## Related content

- `digest.ecdsa_verify()` - Verify ECDSA signatures (ES256).
- `digest.hmac_sha256()` - Compute HMAC-SHA256 for symmetric signature verification.
