Package · jwt

Asymmetric keys (RSA & ECDSA)

When the party that verifies a token shouldn’t be able to forge one, use asymmetric signing: a private key signs, and a freely shareable public key verifies. This guide covers RSA (RS*) and ECDSA (ES*), and the one extra step they require — a kid.

The kid is required

Unlike HMAC, decode() selects an asymmetric verifying key by kid (Key ID) from the header. So when you sign, you must give the PrivateKey a kid, and when you verify, you must key the list by that same id.

No kid, no verification

For any RS*/ES* token, a missing kid in the header throws “Token header is missing ‘kid’”, and a kid with no matching key throws “Key with ‘kid’=… was not found”. Always set the third PrivateKey argument for asymmetric signing.

RSA: sign and verify

Generate a pair (see Installation), then:

sign-rsa.php
<?php

use Flytachi\Jwt\JWT;
use Flytachi\Jwt\Entity\JwtPayload;
use Flytachi\Jwt\Entity\PrivateKey;

$privateKey = openssl_pkey_get_private(file_get_contents('private.pem'));

$token = JWT::encode(
  new JwtPayload(['sub' => 'user-42', 'exp' => time() + 3600]),
  new PrivateKey($privateKey, 'RS256', 'rsa-key-1') // 3rd arg = kid
);

Verify with the public key, keyed by the same kid:

verify-rsa.php
<?php

use Flytachi\Jwt\JWT;
use Flytachi\Jwt\Entity\PublicKey;

$publicKey = openssl_pkey_get_public(file_get_contents('public.pem'));

$payload = JWT::decode($token, [
  'rsa-key-1' => new PublicKey($publicKey, 'RS256'),
]);

echo $payload->getClaim('sub'); // user-42

ECDSA: smaller keys, same flow

ECDSA gives you much shorter keys and signatures for equivalent security. The code is identical apart from the algorithm and curve:

php
use Flytachi\Jwt\JWT;
use Flytachi\Jwt\Entity\JwtPayload;
use Flytachi\Jwt\Entity\PrivateKey;
use Flytachi\Jwt\Entity\PublicKey;

$privateKey = openssl_pkey_get_private(file_get_contents('ec-private.pem'));
$token = JWT::encode(
  new JwtPayload(['sub' => 'user-42', 'exp' => time() + 3600]),
  new PrivateKey($privateKey, 'ES256', 'ec-key-1')
);

$publicKey = openssl_pkey_get_public(file_get_contents('ec-public.pem'));
$payload = JWT::decode($token, [
  'ec-key-1' => new PublicKey($publicKey, 'ES256'),
]);

Curves map to algorithms

ES256 needs a P-256 key, ES384 a P-384 key, and ES512 a P-521 key. The library converts ECDSA’s raw signature to ASN.1 DER for OpenSSL automatically — see the Security model. Generate curves as shown in Installation.

Serve your public keys as a JWKS

Because the public key is safe to publish, the common pattern is to expose it as a JSON Web Key Set that verifiers fetch. You can build one entry from a parsed key, or just publish the standard fields your keys already have:

json
{
"keys": [
  {
    "kty": "RSA",
    "alg": "RS256",
    "kid": "rsa-key-1",
    "use": "sig",
    "n": "0vx7agoebGcQ...",
    "e": "AQAB"
  }
]
}

Consumers then verify without ever touching your private key — see Verifying with JWKS.

Choosing RSA vs ECDSA

RSA (RS*) ECDSA (ES*)
Key size (comparable security) 2048–4096 bit 256–521 bit
Signature size large small
Sign speed slower faster
Verify speed fast fast
Ubiquity universal very wide

Pick RSA for maximum interoperability with older systems; pick ECDSA for smaller tokens and faster signing. Both keep the private key private.