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:
<?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:
<?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-42ECDSA: 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:
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:
{
"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.
Related
- Verifying with JWKS — validate third-party tokens & rotate keys
- Algorithms — the full supported-algorithm matrix
- API reference —
PrivateKey,PublicKey,JWK