Перейти к содержимому
ecdsa.com

ES256 или EdDSA (Ed25519) для JWT

Обе схемы дают 64-байтные подписи и почти одинаковые токены. Выбор зависит от того, какие библиотеки, хранилища ключей и получатели токенов поддерживают каждую из них, а также от нового имени alg для Ed25519.

Выбор между ES256 и EdDSA для JWT — более узкий вопрос, чем выбор между ECDSA и EdDSA вообще. Размеры токенов одинаковые, обе схемы быстрые, и для новых издателей обе рекомендуются вместо RS256. Различается поддержка: ES256 требуют или ожидают многие платформы и хранилища ключей, а Ed25519 проще правильно реализовать, и в JOSE он теперь называется Ed25519, а не EdDSA.

Руководство по выбору между ES256 и RS256 разбирает ту же задачу со стороны RSA, а на этой странице сравниваются два варианта на эллиптических кривых.

Коротко о главном

ES256EdDSA / Ed25519
alg в JOSEES256 (RFC 7518, § 3.4)Ed25519 (RFC 9864); прежний EdDSA (RFC 8037), теперь устаревший
Кривая и хешP-256 с SHA-256Edwards25519 с SHA-512
JWKkty: EC, crv: P-256, x, ykty: OKP, crv: Ed25519, x
Открытый JWK (замер, компактный JSON)126 символов79 символов
Подпись в токене64 байта в формате raw r‖s, 86 символов64 байта, 86 символов
Пример токена (замер ниже)232 символа234 символа (значение alg длиннее)
ДетерминированностьНет (в большинстве библиотек случайный nonce)Да
Уровень требований JWARecommended+ (рекомендуется)Optional (необязателен)
Идентификатор COSE−7 (или −9 ESP256 по RFC 9864)−19 (прежний −8)
Облачные KMSAWS, Google, Azure: даAWS (с ноября 2025 года) и Google: да; Azure: нет
WebCrypto в браузерахВсе браузерыFirefox 129, Safari 17, Chrome 137 и новее

Размер и скорость

Обе подписи занимают 64 байта, поэтому токены различаются по длине только заголовком. Скрипт ниже, запущенный на Node.js 26.10, подписывает одни и те же утверждения обоими способами, используя только node:crypto (в реальном коде следует использовать библиотеку JOSE).

Node.js: JWT на ES256 и Ed25519 с одной и той же полезной нагрузкой
import { generateKeyPairSync, sign } from "node:crypto";

const b64url = (value) => Buffer.from(value).toString("base64url");
const payload = b64url(JSON.stringify({ iss: "https://auth.example.com", sub: "user-42", aud: "api", exp: 1792500000 }));

const es = generateKeyPairSync("ec", { namedCurve: "P-256" });
const esInput = `${b64url(JSON.stringify({ alg: "ES256", kid: "es-1" }))}.${payload}`;
const es256 = `${esInput}.${b64url(sign("sha256", Buffer.from(esInput), { key: es.privateKey, dsaEncoding: "ieee-p1363" }))}`;

const ed = generateKeyPairSync("ed25519");
const edInput = `${b64url(JSON.stringify({ alg: "Ed25519", kid: "ed-1" }))}.${payload}`;
const ed25519 = `${edInput}.${b64url(sign(null, Buffer.from(edInput), ed.privateKey))}`;

console.log("ES256   token", es256.length, "chars, public JWK", JSON.stringify(es.publicKey.export({ format: "jwk" })).length, "chars");
console.log("Ed25519 token", ed25519.length, "chars, public JWK", JSON.stringify(ed.publicKey.export({ format: "jwk" })).length, "chars");
Результат
ES256   token 232 chars, public JWK 126 chars
Ed25519 token 234 chars, public JWK 79 chars

Скорость здесь ничего не решает: на одном ядре Apple M4 Max OpenSSL 3.6.5 создаёт около 87 000 подписей P-256 и 60 000 подписей Ed25519 в секунду. JWKS с ключами Ed25519 меньше, потому что у ключей OKP одна координата.

Кодировка и корректность

Подписи ES256 в JWS должны быть в формате raw r‖s длиной 64 байта (RFC 7518, § 3.4). Многие API для ECDSA по умолчанию выдают DER: OpenSSL, SHA256withECDSA в Java, ecdsa.SignASN1 в Go, crypto.sign() в Node без dsaEncoding, а также API подписи в AWS KMS и Google Cloud KMS. Самописный код для ES256, в котором забыли о преобразовании, выдаёт подписи длиной 70–72 байта, и проверяющие стороны, соблюдающие стандарт, их отклоняют, а обратная ошибка ломает проверку корректных токенов. Кроме того, проверяющие стороны ECDSA должны отклонять r или s, равные нулю; Java версий 15–18 их не отклоняла (CVE-2022-21449), и до выхода исправления кто угодно мог подделать токены ES256.

У Ed25519 одна 64-байтная кодировка, детерминированное подписание и нет nonce, с которым можно ошибиться. Ловушки у него в другом: одни библиотеки считают закрытым ключом 32-байтный seed, другие — 64 байта, а старые библиотеки JOSE распознают только "alg": "EdDSA".

Имя alg изменилось

RFC 8037 (2017) зарегистрировал EdDSA как полиморфный алгоритм JOSE, кривая которого определяется ключом (crv: Ed25519 или Ed448). RFC 9864 (октябрь 2025 года) объявляет полиморфные идентификаторы устаревшими и регистрирует Ed25519 и Ed448, так что операцию определяет один только alg, как это всегда было с ES256. В 2026 году поддержка неравномерна: jose 6 принимает и EdDSA, и Ed25519, joserfc и Nimbus JOSE+JWT поддерживают новые имена, а PyJWT 2.15 и golang-jwt v5 по-прежнему принимают только EdDSA. Если токены принимают многие стороны, проверьте каждого получателя, прежде чем менять значение в заголовке; на переходный период проверяющие стороны могут принимать оба имени для одного и того же ключа.

Поддержка в платформах и хранилищах ключей

ES256 — наименьший общий знаменатель. Его обычно выбирают для доказательств DPoP (RFC 9449), это один из трёх алгоритмов, которые допускает FAPI 2.0 (PS256, ES256 или EdDSA с Ed25519), он обязателен для App Store Server API от Apple и для токенов провайдера APNs, его поддерживают все поставщики OpenID Connect, и это алгоритм COSE по умолчанию в WebAuthn. Ключи P-256 предлагают все HSM, TPM, Secure Enclave и облачные KMS.

В программных реализациях Ed25519 догнал ES256: Node.js, Go, Java 15 и новее, .NET через библиотеки, cryptography в Python, WebCrypto в браузерах и основные библиотеки JOSE. В хранилищах ключей он появился позже: AWS KMS добавил ключи подписи Ed25519 в ноябре 2025 года, Google Cloud KMS предлагает EC_SIGN_ED25519, а некоторые модели HSM и Azure Key Vault его не поддерживают. Если токены нужно подписывать внутри управляемого хранилища ключей, сначала проверьте его список алгоритмов.

Что когда выбирать

  • Публичный поставщик удостоверений, много доверяющих сторон: ES256 (при необходимости с RS256 для старых получателей).
  • Ключи в HSM или KMS в нескольких облаках: ES256, чтобы поддержка была везде одинаковой.
  • Внутренние сервисы, обе стороны под вашим контролем, современные библиотеки: Ed25519 с alg: Ed25519, если его поддерживают все проверяющие стороны, иначе EdDSA.
  • Токены с привязкой к отправителю (DPoP) из браузеров и мобильных приложений: ES256, ключ для которого любое платформенное хранилище ключей может сгенерировать без возможности экспорта.
  • Профили, которые явно задают алгоритмы (FAPI, государственные, финансовые): то, что указано в профиле.

Заметки о миграции

  1. Добавьте новый ключ в JWKS с собственным kid и явно указанным alg и дайте получателям время обновить кеши.
  2. Настройте проверяющие стороны для каждого ключа отдельно: каждому ключу соответствует ровно один допустимый алгоритм (а для Ed25519 — оба значения заголовка, EdDSA и Ed25519, если ваши получатели поддерживают разные имена). Никогда не принимайте alg из токена без такой привязки.
  3. Переключите издателя на новый ключ; уже выданные токены по-прежнему проверяются старым ключом, пока не истечёт их срок действия.
  4. Удалите старый ключ, когда пройдёт максимальный срок жизни токена.
  5. Проверьте кодировки на всём пути: подпись ES256 из KMS нужно преобразовать из DER в raw, прежде чем она попадёт в токен; конвертер DER и raw и отладчик JWT показывают, что находится в токене.

Источники

Все сравнения