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

python-jose

python-jose — библиотека JOSE, которая до сих пор часто встречается в руководствах по FastAPI: необязательный аргумент algorithms, нет поддержки PS и EdDSA, токены ES256 можно подписать ключами P-384, а из-за проблемы, описанной в незакрытом бюллетене безопасности, открытый ключ в DER может служить секретом HMAC.
Лицензия
MIT
Проверенная версия
3.5.0 ·
Исходный код
github.com/mpdavis/python-jose
Документация
python-jose.readthedocs.io/en/latest/

Совместимость вкратце

JOSE и JWT

Алгоритмы JWS
HS256HS384HS512RS256RS384RS512ES256ES384ES512

python-jose реализует JWS, JWE, JWK и JWT со сменными бэкендами: ecdsa на чистом Python, pycryptodome или cryptography; для проверки установили набор опциональных зависимостей cryptography. Благодаря многолетним руководствам по FastAPI и Flask библиотека широко распространена, но развивается медленно: 3.4.0 (февраль 2025 года) вышла почти через 4 года после 3.3.0, и новые проекты обычно выбирают PyJWT, jwcrypto или joserfc.

В jwt.decode() аргумент algorithms необязателен, и без него алгоритм берётся из заголовка токена. Вместе с проблемой, которую описывает опубликованный в сентябре 2026 года бюллетень безопасности GHSA-3qf3-8w2g-rqmx (CVE-2026-85394) и которая не исправлена в 3.5.0, этим можно воспользоваться на практике: защита от использования открытых ключей как секретов HMAC распознаёт текст PEM и OpenSSH, но не тот же ключ в виде байтов DER. В ходе проверки это удалось воспроизвести: токен HS256, ключом HMAC для которого служил открытый ключ в DER, прошёл проверку этим ключом, когда algorithms не передавали, и был отклонён с ошибкой The specified alg value is not allowed, как только передали algorithms=["ES256"]. Всегда передавайте algorithms, храните открытые ключи как строки PEM и проверяйте секреты инструментом проверки секретов JWT.

Набор алгоритмов самый устаревший среди библиотек для Python: только семейства HS, RS и ES. PS256, EdDSA и ES256K вызывают Algorithm … not supported., поэтому токены от поставщиков, перешедших на Ed25519 или RSA-PSS, проверить нельзя. К тому же ES256 не привязан к P-256: ключ P-384 дал 96-байтовую подпись с заголовком ES256, которую python-jose принял при проверке, а другие библиотеки, например jose и PyJWT, отклоняют.

Подписи ES256 — это 64 байта в формате raw r‖s, а подпись DER не проходит проверку с ошибкой Signature verification failed.; в каком формате подпись в токене, показывает отладчик JWT. exp необязателен, а допуск на расхождение часов равен нулю, если не задать require_exp и leeway в options, как в примере.

Неподписанные токены отклонялись с ошибкой Unable to find an algorithm for key, даже с algorithms=["none"].

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/python-jose/run.sh запустила python-jose 3.5.0 в среде Python 3.14.8; installed with the [cryptography] extra; EC key class from jose.backends.cryptography_backend в образе контейнера python:3.14.8-slim-trixie; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Алгоритмы JWS

    HS256HS384HS512RS256RS384RS512ES256ES384ES512

    HS, RS и ES на 256/384/512 бит; PS256/384/512, ES256K, EdDSA, Ed25519 и Ed448 вызвали JWSError: Algorithm … not supported.

    Что записала проверка
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512
    evidence jws-algorithms: PS256: JWSError: Algorithm PS256 not supported.
    evidence jws-algorithms: PS384: JWSError: Algorithm PS384 not supported.
    evidence jws-algorithms: PS512: JWSError: Algorithm PS512 not supported.
    evidence jws-algorithms: ES256K: JWSError: Algorithm ES256K not supported.
    evidence jws-algorithms: EdDSA: JWSError: Algorithm EdDSA not supported.
    evidence jws-algorithms: Ed25519: JWSError: Algorithm Ed25519 not supported.
    evidence jws-algorithms: Ed448: JWSError: Algorithm Ed448 not supported.
  • Кодировка подписи ES256

    raw

    Подписи ES256 — 64 байта в формате raw r‖s.

    Что записала проверка
    fact jws-ecdsa-encoding = raw
  • Подпись в DER внутри JWS

    rejected

    Подпись ES256 в кодировке DER не проходит проверку с ошибкой JWTError: Signature verification failed.

    Что записала проверка
    fact jws-der-signature = rejected
    evidence jws-der-signature: JWTError: Signature verification failed.
  • Список разрешённых алгоритмов

    from-key

    algorithms необязателен: токен ES256 прошёл проверку без него, а токен RS256 с ключом EC отклонён только на этапе проверки подписи.

    Что записала проверка
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: no algorithms argument: verified; RS256 token against the EC key: JWTError: Signature verification failed.
  • Неподписанные токены (alg none)

    rejected

    Неподписанные токены вызывают JWKError: Unable to find an algorithm for key как с algorithms=["none"], так и без него.

    Что записала проверка
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: with the EC key: JWKError: Unable to find an algorithm for key
    evidence jwt-alg-none: algorithms ['none']: JWKError: Unable to find an algorithm for key
  • Открытый ключ в роли секрета HMAC

    accepted

    Открытые ключи PEM и OpenSSH не принимаются как секреты HMAC, но DER-кодировка того же открытого ключа была принята: токен HS256 с этим ключом в роли ключа HMAC прошёл проверку, когда algorithms не передавали (бюллетень GHSA-3qf3-8w2g-rqmx, для 3.5.0 не закрыт).

    Что записала проверка
    fact jwt-hmac-public-key = accepted
    evidence jwt-hmac-public-key: DER public key bytes, no algorithms: verified
    evidence jwt-hmac-public-key: DER public key bytes, algorithms ['ES256']: JWTError: The specified alg value is not allowed
    evidence jwt-hmac-public-key: PEM public key, algorithms ['HS256']: JWKError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
    evidence jwt-hmac-public-key: PEM public key, no algorithms: JWKError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
    evidence jwt-hmac-public-key: OpenSSH public key, algorithms ['HS256']: JWKError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
  • Сверка кривой с alg

    not-enforced

    При подписи ES256 ключом P-384 ошибки не было: получилась 96-байтовая подпись, которую python-jose сам же принял при проверке.

    Что записала проверка
    fact jwt-ecdsa-curve-check = not-enforced
    evidence jwt-ecdsa-curve-check: signing allowed; verification: verified; signature length 96 bytes under an ES256 header
  • Допустимое расхождение часов по умолчанию

    0s

    По умолчанию расхождение часов не допускается: токен, срок действия которого истёк 3 секунды назад, отклонён.

    Что записала проверка
    fact jwt-clock-skew = 0s
  • Обязательное утверждение о сроке действия

    no

    Токен без exp успешно декодируется, если не задан require_exp.

    Что записала проверка
    fact jwt-exp-required = no

Использование

Выпустите токен ES256 и декодируйте его с фиксированными алгоритмом и аудиторией, обязательным exp и допуском на расхождение часов в 30 секунд.
import time

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from jose import jwt

key = ec.generate_private_key(ec.SECP256R1())
private_pem = key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()).decode()
public_pem = key.public_key().public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo).decode()

token = jwt.encode({"sub": "user-42", "aud": "api", "exp": int(time.time()) + 600}, private_pem, algorithm="ES256")

# Always pass algorithms: without it python-jose takes the algorithm from the token header.
claims = jwt.decode(token, public_pem, algorithms=["ES256"], audience="api", options={"require_exp": True, "leeway": 30})
print("sub:", claims["sub"])

Тестовый стенд для фрагментов кода запускает каждый пример без изменений в образе контейнера python:3.14.8-slim-trixie, и запуск завершается ошибкой, если вывод не совпадает.

Подготовка
pip install --quiet --root-user-action=ignore 'python-jose[cryptography]==3.5.0' cryptography==50.0.2
Команда
python usage.py
Вывод должен совпадать (регулярное выражение)
sub: user-42

Библиотеки