Saltar al contenido
ecdsa.com
Python

python-jose

python-jose, una biblioteca JOSE todavía habitual en los tutoriales de FastAPI: algorithms opcional, sin PS ni EdDSA, tokens ES256 firmados con claves P-384 y un aviso de seguridad abierto que permite que una clave pública DER actúe como secreto HMAC.
Licencia
MIT
Versión comprobada
3.5.0 ·
Código fuente
github.com/mpdavis/python-jose
Documentación
python-jose.readthedocs.io/en/latest/

Compatibilidad de un vistazo

JOSE y JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512ES256ES384ES512

python-jose implementa JWS, JWE, JWK y JWT con backends intercambiables —ecdsa en Python puro, pycryptodome o cryptography—; la comprobación instaló el extra cryptography. Años de tutoriales de FastAPI y Flask la popularizaron, pero el desarrollo es lento: la 3.4.0 (febrero de 2025) llegó casi 4 años después de la 3.3.0, y los proyectos nuevos suelen elegir PyJWT, jwcrypto o joserfc en su lugar.

algorithms es opcional en jwt.decode(), y sin él el algoritmo sale del encabezado del token. Junto con el aviso de seguridad GHSA-3qf3-8w2g-rqmx (CVE-2026-85394), publicado en septiembre de 2026 y sin corregir en la 3.5.0, eso es explotable: la protección contra el uso de claves públicas como secretos HMAC reconoce el texto PEM y OpenSSH, pero no la misma clave en bytes DER. La comprobación lo reprodujo: un token HS256 cuya clave HMAC era la clave pública DER se verificó contra esa clave cuando se omitió algorithms, y se rechazó con The specified alg value is not allowed en cuanto se pasó algorithms=["ES256"]. Pasa siempre algorithms, conserva las claves públicas como cadenas PEM y comprueba los secretos con el comprobador de secretos JWT.

Su conjunto de algoritmos es el más antiguo de las bibliotecas de Python: solo las familias HS, RS y ES. PS256, EdDSA y ES256K lanzan Algorithm … not supported., así que los tokens de proveedores que se pasaron a Ed25519 o a RSA-PSS no se pueden verificar. ES256 tampoco está ligado a P-256: una clave P-384 produjo una firma de 96 bytes bajo un encabezado ES256 que python-jose verificó, pero que otras bibliotecas, como jose y PyJWT, rechazan.

Las firmas ES256 son 64 bytes de r‖s en bruto, y una firma DER falla con Signature verification failed.; el decodificador de JWT muestra qué forma lleva un token. exp es opcional y la tolerancia es cero salvo que configures require_exp y leeway en options, como hace el ejemplo.

Los tokens sin firmar se rechazaron con Unable to find an algorithm for key, incluso con algorithms=["none"].

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/python-jose/run.sh ejecutó python-jose 3.5.0 en Python 3.14.8; installed with the [cryptography] extra; EC key class from jose.backends.cryptography_backend en la imagen de contenedor python:3.14.8-slim-trixie el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512ES256ES384ES512

    HS, RS y ES de 256/384/512 bits; PS256/384/512, ES256K, EdDSA, Ed25519 y Ed448 lanzaron JWSError: Algorithm … not supported.

    Registrado por la comprobación
    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.
  • Codificación de la firma ES256

    raw

    Las firmas ES256 son 64 bytes de r‖s en bruto.

    Registrado por la comprobación
    fact jws-ecdsa-encoding = raw
  • Firma DER dentro de un JWS

    rejected

    Una firma ES256 codificada en DER falla con JWTError: Signature verification failed.

    Registrado por la comprobación
    fact jws-der-signature = rejected
    evidence jws-der-signature: JWTError: Signature verification failed.
  • Lista de algoritmos permitidos

    from-key

    algorithms es opcional: un token ES256 se verificó sin él, y un token RS256 contra la clave EC solo falló en la verificación de la firma.

    Registrado por la comprobación
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: no algorithms argument: verified; RS256 token against the EC key: JWTError: Signature verification failed.
  • Tokens sin firma (alg none)

    rejected

    Los tokens sin firmar lanzan JWKError: Unable to find an algorithm for key, con o sin algorithms=["none"].

    Registrado por la comprobación
    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
  • Clave pública usada como secreto HMAC

    accepted

    Las claves públicas PEM y OpenSSH se rechazan como secretos HMAC, pero la codificación DER de esa misma clave pública se aceptó: un token HS256 con ella como clave se verificó cuando se omitió algorithms (aviso de seguridad GHSA-3qf3-8w2g-rqmx, abierto para la 3.5.0).

    Registrado por la comprobación
    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.
  • Curva cotejada con alg

    not-enforced

    Firmar ES256 con una clave P-384 funcionó y produjo una firma de 96 bytes que el propio python-jose verificó.

    Registrado por la comprobación
    fact jwt-ecdsa-curve-check = not-enforced
    evidence jwt-ecdsa-curve-check: signing allowed; verification: verified; signature length 96 bytes under an ES256 header
  • Desfase de reloj predeterminado

    0s

    Sin tolerancia por defecto: se rechazó un token que había caducado 3 segundos antes.

    Registrado por la comprobación
    fact jwt-clock-skew = 0s
  • Claim de caducidad obligatorio

    no

    Un token sin exp se decodificó correctamente salvo con require_exp activado.

    Registrado por la comprobación
    fact jwt-exp-required = no

Uso

Emite un token ES256 y decodifícalo con el algoritmo y la audiencia fijados, exp obligatorio y 30 segundos de tolerancia.
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"])

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor python:3.14.8-slim-trixie, y la ejecución falla si la salida no coincide.

Configuración
pip install --quiet --root-user-action=ignore 'python-jose[cryptography]==3.5.0' cryptography==50.0.2
Comando
python usage.py
La salida debe coincidir (expresión regular)
sub: user-42

Bibliotecas