digest.rsa_verify
Available inall 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. This is the signature scheme used by:
- RS256, RS384, RS512 algorithms in JWTs (RFC 7518 Section 3.3).
- OpenSSL's
openssl dgst -signcommand 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
declare local var.verified BOOL;
set var.verified = digest.rsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpb4ATDPigeVvSfmxqIez0LGD87v4Qzu5nWMlknPlS9pBkz+++MkBZwJFjXLwmqB0KvdABo3fErO5YkgO7UCayLXLLk2WBoQ4IvS+L+P+yheR8D+0AJGpNzy9R8kC99RlCdcL37BczHUfaT6WZjsiFvR9SP2emAY4sk+Rcyu7EPsyoa7JCYSDhYGE8ljPB1MLQLGFpJQDnpOD/SSqfz97PNWpLI6OGNqX4u+6fexrvbRw31ps7FUyXtiq3oDWKBwsca9cj31n0vfjmQdle+1LDhrMxbKuOzgf25c6fkRgTU9GgxiwuMqsjfQICj5y2wdigjU2kE+Wz1a/hkzz6t1KwIDAQAB-----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:
$ 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:
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 algorithmset var.jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.jWE265FCZzjhybC8Cy-cMe2YdzzBYrbhHDp9cSocle_jS90ZvRYANwqyl3mUr7PLLXTRDDHy9wCULMuHaw2U_rCJfSXlaqMP8EOHRfAAkPmyKI2GQ9RGiq_JaBGd8QBRNaZgQZdM961J4KcSnRVfgLaznykvUS7fxNq64157hM6d8n29P6_nV3uJkz-viywD6YlDWTuL_It4nDXgQhSS4Xk7FzuDdBKFQo9l8Z2HDVOYM15VQ43lcXCgHMaGMH_1FCLLCP1hpu4C1a9tQLZ8LhVOrkS_KVhbYijQA0NRrxd40MEQwQGoDiExSa_tkJ9wRum53AvBf0Mvl5Y5vd_szQ";
# Extract header.payload and signature from the JWTif (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 signatureset var.verified = digest.rsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqaBIzfdfRoyMA5iFbGOpJWwSezwkz9CmjL2dE/46kQFvj9Mr4R5OZDpqUEGLoTC52z8IuhOESMBr8L9F+KTdxZN00VHzjVvYUvVyV7xgTV3yhroOAewLlAW8ZwlBtinTEATIqEkCxyhaNfktyJzXSMgS25eZltEsGVavIzwxLqA18eaCmMReB9BwFh2j0bUo/hLBD8AGZIREbt7e1E5xnbjBZb+On2Qiq1bUYR/SsyXvFIOo73f/712LZ97P1n6lGAj5LTq4EDVHVJDK9QAGlBP6EU2+6fBBlm2yNhsnjDELZEMDWFUQNibdjNcsHy5tK7X5+8PgPhFurPiO9owcjwIDAQAB-----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:
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-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqaBIzfdfRoyMA5iFbGOpJWwSezwkz9CmjL2dE/46kQFvj9Mr4R5OZDpqUEGLoTC52z8IuhOESMBr8L9F+KTdxZN00VHzjVvYUvVyV7xgTV3yhroOAewLlAW8ZwlBtinTEATIqEkCxyhaNfktyJzXSMgS25eZltEsGVavIzwxLqA18eaCmMReB9BwFh2j0bUo/hLBD8AGZIREbt7e1E5xnbjBZb+On2Qiq1bUYR/SsyXvFIOo73f/712LZ97P1n6lGAj5LTq4EDVHVJDK9QAGlBP6EU2+6fBBlm2yNhsnjDELZEMDWFUQNibdjNcsHy5tK7X5+8PgPhFurPiO9owcjwIDAQAB-----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:
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:
# RS256 - SHA-256 (most common)set var.verified = digest.rsa_verify(sha256, var.public_key, var.message, var.signature, url_nopad);
# RS384 - SHA-384set var.verified = digest.rsa_verify(sha384, var.public_key, var.message, var.signature, url_nopad);
# RS512 - SHA-512set 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:
declare local var.verified BOOL;
set var.verified = digest.rsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpb4ATDPigeVvSfmxqIez0LGD87v4Qzu5nWMlknPlS9pBkz+++MkBZwJFjXLwmqB0KvdABo3fErO5YkgO7UCayLXLLk2WBoQ4IvS+L+P+yheR8D+0AJGpNzy9R8kC99RlCdcL37BczHUfaT6WZjsiFvR9SP2emAY4sk+Rcyu7EPsyoa7JCYSDhYGE8ljPB1MLQLGFpJQDnpOD/SSqfz97PNWpLI6OGNqX4u+6fexrvbRw31ps7FUyXtiq3oDWKBwsca9cj31n0vfjmQdle+1LDhrMxbKuOzgf25c6fkRgTU9GgxiwuMqsjfQICj5y2wdigjU2kE+Wz1a/hkzz6t1KwIDAQAB-----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:
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 hereset 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 signatureif (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 cryptoif (digest.base64url_nopad_decode(var.header) !~ {""alg"\s*:\s*"RS256"}) { error 401 "Unsupported algorithm";}
# Verify with RS256if (!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.
Try it out
digest.rsa_verify is used in the following code examples. Examples apply VCL to real-world use cases and can be deployed as they are, or adapted for your own service. See the full list of code examples for more inspiration.
Click RUN on a sample below to provision a Fastly service, execute the code on Fastly, and see how the function behaves.
Authenticate JSON Web Tokens at the edge
Decode the popular JWT format to verify user session tokens before forwarding trusted authentication data to your origin.