Saltar al contenido
ecdsa.com
Python

PyJWT

PyJWT, de José Padilla, la biblioteca JWT más usada en Python: la lista explícita algorithms es obligatoria, alg: none nunca se verifica y las claves públicas se rechazan como secretos HMAC, pero la caducidad es opcional y la tolerancia es cero.
Licencia
MIT
Versión comprobada
2.15.1 ·
Código fuente
github.com/jpadilla/pyjwt
Documentación
pyjwt.readthedocs.io/en/stable/

Compatibilidad de un vistazo

JOSE y JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

PyJWT codifica y decodifica JSON Web Tokens con jwt.encode() y jwt.decode(), y obtiene las claves de firma de un endpoint JWKS con PyJWKClient. Los algoritmos asimétricos vienen del extra crypto, que instala pyca/cryptography; con él, la 2.15.1 admitió las familias HS, RS, PS y ES, ES256K y EdDSA, pero no los nombres más recientes Ed25519 y Ed448, que lanzaron NotImplementedError: Algorithm not supported.

Sus valores predeterminados de verificación son estrictos. jwt.decode() sin algorithms falla con It is required that you pass in a value for the "algorithms" argument, un token alg: none sin firmar nunca se verificó —ni siquiera con algorithms=["none"]— y una clave pública PEM u OpenSSH usada como secreto HS256 se rechazó con InvalidKeyError. ES256 está ligado a P-256: firmar con una clave P-384 falló por una discrepancia de curva.

Lo que deja en tus manos: exp es opcional, así que un token sin él se decodifica indefinidamente salvo que lo incluyas en require dentro del argumento options, y leeway vale cero por defecto, así que servidores con unos segundos de diferencia ya discrepan sobre la caducidad. Los secretos HMAC cortos se aceptan con solo un InsecureKeyLengthWarning, fácil de pasar por alto en los registros: prueba un secreto con el comprobador de secretos JWT.

Las firmas ES256 tienen la forma r‖s en bruto de 64 bytes que exige JWS; un token cuya firma se armó en DER —por ejemplo, a partir de una llamada de firma a un KMS— falla con el error genérico Signature verification failed. Pega el token en el decodificador de JWT para ver la longitud de la firma y conviértela con el conversor DER ⇄ en bruto.

El ejemplo fija el algoritmo, el emisor y la audiencia, exige exp y admite 30 segundos de desfase: los cuatro ajustes que hacen que sea seguro llamar a jwt.decode() con tokens de otro servicio.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/pyjwt/run.sh ejecutó PyJWT 2.15.1 en cryptography 50.0.2 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

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

    Con HS, RS, PS y ES de 256/384/512 bits, ES256K y EdDSA se firmó y se verificó; Ed25519 y Ed448 lanzaron NotImplementedError: Algorithm not supported.

    Registrado por la comprobación
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, ES256K, EdDSA
    evidence jws-algorithms: Ed25519: NotImplementedError: Algorithm not supported
    evidence jws-algorithms: Ed448: NotImplementedError: Algorithm 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

    Un token con una firma ES256 codificada en DER falla con InvalidSignatureError: Signature verification failed.

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

    required

    jwt.decode() sin algorithms lanza DecodeError: It is required that you pass in a value for the "algorithms" argument when calling decode().

    Registrado por la comprobación
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: DecodeError: It is required that you pass in a value for the "algorithms" argument when calling decode().
  • Tokens sin firma (alg none)

    rejected

    Un token sin firmar se rechaza con The specified alg value is not allowed contra una clave ES256 y con Signature verification failed incluso con algorithms=["none"]; solo verify_signature: False permite leerlo.

    Registrado por la comprobación
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: with an ES256 key: InvalidAlgorithmError: The specified alg value is not allowed
    evidence jwt-alg-none: algorithms ['none'] and no key: InvalidSignatureError: Signature verification failed
    evidence jwt-alg-none: verify_signature False: returned claims
  • Clave pública usada como secreto HMAC

    rejected

    Una clave pública PEM u OpenSSH usada como secreto HS256 lanza InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.

    Registrado por la comprobación
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: PEM public key as HS256 secret: InvalidKeyError: 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 as HS256 secret: InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
  • Curva cotejada con alg

    enforced

    Firmar ES256 con una clave P-384 lanza The key's curve 'secp384r1' does not match the expected curve 'secp256r1' for this algorithm.

    Registrado por la comprobación
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: InvalidKeyError: The key's curve 'secp384r1' does not match the expected curve 'secp256r1' for this algorithm
  • 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; para exigirlo, incluye exp en require dentro del argumento options.

    Registrado por la comprobación
    fact jwt-exp-required = no
  • Secreto HMAC corto

    accepted-with-warning

    Un secreto HMAC de 5 bytes sirvió para firmar y verificar, con un InsecureKeyLengthWarning como única señal.

    Registrado por la comprobación
    fact jwt-hmac-short-secret = accepted-with-warning
    evidence jwt-hmac-short-secret: 5-byte HMAC secret: accepted; warnings: InsecureKeyLengthWarning

Uso

Emite un token ES256 y decodifícalo con el algoritmo, el emisor y la audiencia fijados, exp obligatorio y 30 segundos de tolerancia.
import datetime

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

key = ec.generate_private_key(ec.SECP256R1())
now = datetime.datetime.now(tz=datetime.timezone.utc)

token = jwt.encode(
    {"sub": "user-42", "iss": "https://auth.example.com", "aud": "api", "exp": now + datetime.timedelta(minutes=10)},
    key,
    algorithm="ES256",
)

# algorithms is mandatory; also require exp and allow a little clock drift.
claims = jwt.decode(
    token,
    key.public_key(),
    algorithms=["ES256"],
    audience="api",
    issuer="https://auth.example.com",
    leeway=30,
    options={"require": ["exp", "iss", "aud"]},
)
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 'PyJWT[crypto]==2.15.1' cryptography==50.0.2
Comando
python usage.py
La salida debe coincidir (expresión regular)
sub: user-42

Bibliotecas