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:
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 membersOr call the classes the facade fronts directly:
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 | OkpKeyMembers 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:
$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 membersThumbprint 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.
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:
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 cryptoBy donating, you agree to our donation terms.
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.