---
title: "JWK & thumbprints — Crypto for Laravel | Roundly"
description: "Serialize public keys as RFC 7517 JWKs and back, compute RFC 7638 thumbprints for ACME, and parse untrusted JWKs strictly."
url: https://roundly-consulting.com/open-source/docs/crypto-for-laravel/jwk
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Crypto for Laravel](https://roundly-consulting.com/open-source/docs/crypto-for-laravel.md)

# JWK & thumbprints

A JWK is a public key’s JSON form. Jose\\Jwk goes both ways, and its RFC 7638 thumbprint is what an ACME key authorization is built from:

```php
use RoundlyConsulting\Crypto\Facades\Crypto;

$jwk = Crypto::jwk(Crypto::keys()->ec()->private($pem));   // a P-384 key ⇒ crv P-384
$jwk->thumbprint();                                         // RFC 7638

$key = Crypto::jwkFromJson($json)->publicKey();             // EcKey | RsaKey | OkpKey
$jwk = Crypto::jwkFromArray($members);                      // strict parse of decoded members
```

Or call the classes the facade fronts directly:

```php
use RoundlyConsulting\Crypto\Jose\Jwk;
use RoundlyConsulting\Crypto\Signature\Key\EcKey;

$jwk = Jwk::fromPublicKey(EcKey::private($pem));   // a P-384 key ⇒ crv P-384, 48-byte coordinates

$jwk->algorithm();          // Algorithm::ES384 — derived from kty + crv, never read from `alg`
$jwk->thumbprint();         // base64url(sha256(canonical JSON)) — RFC 7638
$keyAuthorization = $token.'.'.$jwk->thumbprint();          // RFC 8555 §8.1

// It is JsonSerializable, so it drops straight into a JOSE protected header:
$protected = ['alg' => $jwk->algorithm()->value, 'jwk' => $jwk, 'nonce' => $nonce, 'url' => $url];

// …and back again, into a policy-checked verification key:
$key = Jwk::fromJson($json)->publicKey();          // EcKey | RsaKey | OkpKey
```

## Members and accessors

Optional members (kid, alg, use) are carried in toArray() but never thumbprinted. withKid(), withAlg() and withUse() return a copy; alg must be one the key admits and use must be sig:

```php
$jwk->withKid('k1')->thumbprint();   // unchanged — kid, alg and use are never thumbprinted
$jwk->withKid('k1')->toArray();      // members, lexicographically ordered
$jwk->keyType();                     // JwkKeyType::Ec
$jwk->thumbprintRaw();               // the raw SHA-256 digest bytes
$jwk->requiredMembers();             // the RFC 7638 thumbprint input

$jwk = Jwk::fromArray($members);     // strict parse of already-decoded members
```

## Thumbprint input

Only the required members for the key type feed the thumbprint — sorted, JSON-encoded without whitespace, hashed (SHA-256 by default) and base64url-encoded. This value is wire-critical: a one-byte drift breaks certificate issuance at the CA, so it is pinned by frozen test vectors:

| kty | Required members |
| --- | --- |
| `EC` | crv, kty, x, y |
| `RSA` | e, kty, n |
| `OKP` | crv, kty, x |

## Strict parsing

RFC 7517 says unknown members should be ignored; this package rejects them, because a member you silently carry is a member an attacker chose. Everything below throws MalformedJwkException, naming the member and the reason:

- Unknown members (x5c, key\_ops, …) — the whitelist is per kty, so n on an EC key is as unknown as x5c.
- Any private member: d, p, q, dp, dq, qi, oth, k — a JWK is never a private key here.
- Non-base64url values, and coordinates whose length contradicts the stated crv.
- A non-minimal RSA n or e (a leading zero octet).
- An alg the key does not admit — the curve’s own for EC and OKP, an RS\* tier for RSA (ES256, HS256, PS256 or none on an RSA key is refused) — or a use other than sig.
- Documents over 16 KiB (Jwk::MAX\_JSON\_BYTES); members over 8 KiB are refused before they are decoded.

The input of fromArray() / fromJson() — and of Crypto::jwkFromArray() / jwkFromJson() — is #\[\\SensitiveParameter\], as is every parse step that sees it before private members are refused, so a private or symmetric JWK handed over by mistake stays out of the trace of the MalformedJwkException that refuses it.

publicKey() applies the package’s key policy (RSA 2048–8192 bits with a sane exponent, a supported curve) and returns an EcKey, RsaKey or OkpKey.

## The algorithm a JWK pins

For EC and OKP keys algorithm() is derived from kty and crv — the curve fixes the algorithm, so the alg member can only agree with it. An RSA key fits every RS\* tier, so an RSA JWK’s validated alg (RS256, RS384 or RS512) names the tier and algorithm() returns it — RS256 when the member is absent. Verify with that tier:

```php
use RoundlyConsulting\Crypto\Jose\Jwk;
use RoundlyConsulting\Crypto\Signature\Rs;

// An RSA JWK's alg names the tier — RS256, RS384 or RS512 (RS256 when absent):
$jwk = Jwk::fromJson($json);
$verifier = new Rs($jwk->publicKey(), $jwk->algorithm());
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/crypto-for-laravel.md)
