Saltar al contenido
ecdsa.com
Python

JWCrypto

JWCrypto, de Red Hat, la implementación de JOSE que usan FreeIPA y muchos servicios empresariales en Python: cobertura completa de JWS/JWE/JWK, ML-DSA incluido, una tolerancia predeterminada de 60 segundos y alg: none solo si se pide.
Licencia
LGPL-3.0-or-later
Versión comprobada
1.6.1 ·
Código fuente
github.com/latchset/jwcrypto
Documentación
jwcrypto.readthedocs.io/en/latest/

Compatibilidad de un vistazo

JOSE y JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSAEd25519Ed448ML-DSA-44ML-DSA-65ML-DSA-87

JWCrypto implementa la familia JOSE sobre pyca/cryptography: JWK y conjuntos de JWK, JWS, JWE y JWT. Todo se basa en objetos de clave —un objeto jwk.JWK creado a partir de PEM, JSON o generate()—, lo que hace explícito el tipo de clave en cada llamada. Su licencia es LGPL-3.0-or-later, algo que importa si redistribuyes copias modificadas.

Su cobertura de algoritmos es la más amplia de las bibliotecas JOSE de Python: las familias HS, RS, PS y ES, ES256K, EdDSA y los nombres totalmente especificados Ed25519 y Ed448, y en la 1.6.1 también ML-DSA-44, ML-DSA-65 y ML-DSA-87 con claves AKP del borrador de JOSE poscuántico; con todos ellos se firmó y se verificó en la comprobación, y todos están en la lista de algoritmos permitidos predeterminada. Esa lista predeterminada es la trampa: salvo que pases algs, cualquiera de ellos se acepta para una clave compatible, así que limítala a lo que tu emisor usa realmente, como hace el ejemplo.

Los tokens sin firmar se rechazan con Algorithm not allowed salvo que none esté en la lista, y un token HS256 presentado con una JWK pública EC falla, porque ese tipo de clave no puede calcular un HMAC. ES256 rechaza una clave P-384 con Curve requested is "P-256", but key curve is "P-384". Las firmas ES256 tienen la forma r‖s en bruto de 64 bytes; una firma DER falla con Verification failed for all signatures: comprueba la codificación en el decodificador de JWT.

Dos valores predeterminados difieren de los de otras bibliotecas. Las comprobaciones de caducidad admiten 60 segundos de desfase de reloj: un token que había caducado 57 segundos antes aún se validó y uno que había caducado 63 segundos antes, no, lo que resulta más permisivo que PyJWT o jose, que no admiten ninguno. Además, exp es opcional: un token sin él se validó salvo que check_claims lo nombre.

Crea conjuntos de claves con el constructor de JWKS e inspecciona los tokens cifrados con el inspector de JWE; para claves y firmas ML-DSA, el laboratorio de firmas poscuánticas muestra los tamaños en juego.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/jwcrypto/run.sh ejecutó JWCrypto 1.6.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

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSAEd25519Ed448ML-DSA-44ML-DSA-65ML-DSA-87

    Con HS, RS, PS y ES de 256/384/512 bits, ES256K, EdDSA, Ed25519, Ed448 y ML-DSA-44/65/87 (con claves AKP) se firmó y se verificó; todos están en la lista de algoritmos permitidos predeterminada.

    Registrado por la comprobación
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, ES256K, EdDSA, Ed25519, Ed448, ML-DSA-44, ML-DSA-65, ML-DSA-87
    evidence jws-algorithms: ML-DSA-44: JWK kty AKP
    evidence jws-algorithms: ML-DSA-65: JWK kty AKP
    evidence jws-algorithms: ML-DSA-87: JWK kty AKP
    evidence jws-algorithms: default allowed list: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, EdDSA, ES256K, Ed25519, Ed448, ML-DSA-44, ML-DSA-65, ML-DSA-87
  • 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 InvalidJWSSignature: Verification failed for all signatures.

    Registrado por la comprobación
    fact jws-der-signature = rejected
    evidence jws-der-signature: InvalidJWSSignature: Verification failed for all signatures["Failed: [InvalidJWSSignature('Verification failed')]"]
  • Lista de algoritmos permitidos

    from-key

    Sin algs, se aplica la lista predeterminada y decide el tipo de clave: un token ES256 se verificó con una clave EC, y un token RS256 contra esa misma clave falló.

    Registrado por la comprobación
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: no algs argument: verified; RS256 token with the EC key: InvalidJWSSignature: Verification failed for all signatures["Failed: [InvalidJWSSignature('Verification failed')]"]
  • Tokens sin firma (alg none)

    opt-in

    Por defecto, un token sin firmar falla con Algorithm not allowed y solo se verifica con algs=["none"].

    Registrado por la comprobación
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: with the EC key: InvalidJWSSignature: Verification failed for all signatures["Failed: [InvalidJWSOperation('Algorithm not allowed')]"]
    evidence jwt-alg-none: algs ['none'] with an empty oct key: verified
  • Clave pública usada como secreto HMAC

    rejected

    Un token HS256 comprobado contra la JWK pública EC falló; solo lo verificó una JWK oct construida a propósito con el texto PEM.

    Registrado por la comprobación
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: HS256 token checked with the EC public JWK: InvalidJWSSignature: Verification failed for all signatures["Failed: [InvalidJWSSignature('Verification failed')]"]
    evidence jwt-hmac-public-key: only an explicit oct JWK made from the PEM text verifies it: True
  • Curva cotejada con alg

    enforced

    Firmar ES256 con una clave P-384 lanza Curve requested is "P-256", but key curve is "P-384".

    Registrado por la comprobación
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: InvalidJWKValue: Curve requested is "P-256", but key curve is "P-384"
  • Desfase de reloj predeterminado

    60s

    La caducidad admite por defecto 60 segundos de desfase: un token caducado 57 segundos antes se validó y uno caducado 63 segundos antes, no.

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

    no

    Un token sin exp se validó; incluye exp en check_claims para exigirlo.

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

Uso

Firma un token ES256 con un ID de clave y verifícalo con la lista de algoritmos restringida y con iss y exp obligatorios.
import json
import time

from jwcrypto import jwk, jwt

key = jwk.JWK.generate(kty="EC", crv="P-256", kid="2026-10")
public = jwk.JWK.from_json(key.export_public())

token = jwt.JWT(header={"alg": "ES256", "kid": "2026-10"}, claims={"sub": "user-42", "iss": "https://auth.example.com", "exp": int(time.time()) + 600})
token.make_signed_token(key)

# algs narrows the default list; check_claims makes iss and exp mandatory.
checked = jwt.JWT(
    jwt=token.serialize(),
    key=public,
    algs=["ES256"],
    check_claims={"iss": "https://auth.example.com", "exp": None},
)
print("sub:", json.loads(checked.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 jwcrypto==1.6.1 cryptography==50.0.2
Comando
python usage.py
La salida debe coincidir (expresión regular)
sub: user-42

Bibliotecas