Пакет · jwt

Асимметричные ключи (RSA и ECDSA)

Когда сторона, проверяющая токен, не должна иметь возможности его подделать, используйте асимметричную подпись: приватный ключ подписывает, а свободно распространяемый публичный ключ проверяет. Это руководство охватывает RSA (RS*) и ECDSA (ES*), а также один дополнительный шаг, который они требуют, — kid.

kid обязателен

В отличие от HMAC, decode() выбирает асимметричный проверяющий ключ по kid (идентификатору ключа) из заголовка. Поэтому при подписании вы должны передать PrivateKey значение kid, а при проверке — указать список с ключом по этому же идентификатору.

Нет kid — нет проверки

Для любого токена RS*/ES* отсутствие kid в заголовке вызывает ошибку “Token header is missing ‘kid’”, а kid без соответствующего ключа вызывает “Key with ‘kid’=… was not found”. Всегда указывайте третий аргумент PrivateKey для асимметричной подписи.

RSA: подпись и проверка

Сгенерируйте пару (см. «Установка и требования»), затем:

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') // 3-й аргумент = kid
);

Проверьте публичным ключом, указав тот же 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: меньшие ключи, тот же процесс

ECDSA даёт вам гораздо более короткие ключи и подписи при эквивалентной безопасности. Код идентичен, за исключением алгоритма и кривой:

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'),
]);

Кривые соответствуют алгоритмам

ES256 требует ключ P-256, ES384 — ключ P-384, а ES512 — ключ P-521. Библиотека автоматически преобразует сырую подпись ECDSA в ASN.1 DER для OpenSSL — см. «Модель безопасности». Генерируйте кривые, как показано в «Установка и требования».

Публикуйте свои публичные ключи как JWKS

Поскольку публичный ключ безопасно публиковать, распространённый подход — предоставить его как JSON Web Key Set, который получают проверяющие стороны. Вы можете собрать одну запись из разобранного ключа или просто опубликовать стандартные поля, которые уже есть в ваших ключах:

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

Потребители затем проверяют токены, ни разу не касаясь вашего приватного ключа — см. «Проверка через JWKS».

Выбор между RSA и ECDSA

RSA (RS*) ECDSA (ES*)
Размер ключа (сопоставимая безопасность) 2048–4096 бит 256–521 бит
Размер подписи большой малый
Скорость подписи медленнее быстрее
Скорость проверки быстрая быстрая
Распространённость универсальна очень широкая

Выбирайте RSA для максимальной совместимости со старыми системами; выбирайте ECDSA для меньших токенов и более быстрого подписания. Оба варианта держат приватный ключ приватным.

Связанные материалы