jwcrypto

Python implementation of the full JOSE specification set for JSON Web Signing, Encryption, Keys, and Tokens.

Library
PyPI
v1.5.9
478stars
LGPL-3.0-or-later

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
70/100Good
Development Activity72
Maintenance48
Community80
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
76/100Good
Architecture85
Code Quality78
Innovation80
Learning Curve60

jwcrypto is a Python library that implements the complete JOSE (JSON Object Signing and Encryption) family of IETF specifications: JWK for key representation, JWS for signing, JWE for encryption, JWA for the underlying algorithms, and JWT for compact tokens built on top of JWS/JWE. Rather than bolting JWT support onto a partial implementation, it builds tokens from the same JWK/JWS/JWE primitives applications need for key management and signed or encrypted payloads individually.

Maintained by the Latchset project (the team behind FreeIPA and related Red Hat identity infrastructure), it wraps python-cryptography for the actual crypto operations and focuses on spec-correct parsing, serialization, and validation — including support for post-quantum ML-DSA signatures alongside RSA, EC, HMAC, and EdDSA algorithms.

What You Get

  • JWK key objects - Generate, import, and export JSON Web Keys in symmetric, RSA, EC, OKP, and (newer) AKP/ML-DSA formats, plus JWKSet containers for key rotation.
  • JWS signing/verification - Compact and JSON serialization support for HMAC, RSA, ECDSA, EdDSA, and post-quantum ML-DSA signature algorithms.
  • JWE encryption/decryption - Full key-management (RSA-OAEP, AES-KW, ECDH-ES, PBES2) and content-encryption (AES-CBC-HMAC, AES-GCM) algorithm coverage.
  • JWT token handling - Claims validation with configurable expiry/leeway, required-claim enforcement, and automatic detection of JWS vs JWE token types.
  • RFC-driven test suite - Test vectors drawn directly from RFC 7520’s worked examples, run across Python 3.9 through 3.14 in CI.

Common Use Cases

  • Issuing signed API tokens - A backend service signs JWTs with an RSA or EC key so downstream services can verify caller identity without a shared secret.
  • Encrypting sensitive claims - An application uses JWE to encrypt PII or credentials inside a token payload before handing it to a client or third party.
  • Key rotation for JWKS endpoints - A service publishes a JWKSet at a /.well-known/jwks.json endpoint and rotates signing keys using jwcrypto’s key management.
  • Implementing OAuth2/OIDC token handling - Identity provider or resource server code parses and validates ID tokens and access tokens compliant with OIDC’s JOSE requirements.

Under The Hood

Architecture jwcrypto is organized as a small set of single-purpose modules rather than one monolithic implementation. common.py holds base64url encoding helpers, JSON encode/decode wrappers, and the shared JWException hierarchy every other module raises from. jwk.py defines JWK as a dict subclass — keys behave like ordinary Python dicts while gaining generate()/from_pem()/export() methods — plus a JWKSet container for grouping keys by key ID. jwa.py is the algorithm registry: dozens of private classes (_HS256, _RS256, _EcdhEs, _A128GcmKw, and so on) each implement a shared JWAAlgorithm abstract base for signing, verifying, or key-wrapping, and JWS/JWE look algorithms up by name from this registry rather than branching on type internally. JWSCore in jws.py is the low-level primitive that JWS wraps with serialization and validation logic, and JWT in jwt.py sits one layer above both JWS and JWE — it detects at construction time whether a token is a signed or encrypted type and delegates accordingly. Changing the core JWK representation would ripple through every other module since all of them consume JWK objects directly, but the algorithm registry pattern in jwa.py means adding a new algorithm (as happened with the recent ML-DSA post-quantum signatures) doesn’t require touching JWS or JWE at all.

Tech Stack The only runtime dependencies are cryptography (>=49.0.0, for every actual crypto primitive — RSA, EC, AES, HMAC, EdDSA, and now ML-DSA) and typing_extensions (>=4.5.0, used narrowly for a @deprecated decorator). The package is built with Hatchling and declares support for Python 3.9 through 3.14. There’s no web framework, ORM, or CLI surface — it’s a pure library meant to be imported. Documentation is built with Sphinx and hosted on Read the Docs, with per-module .rst files under docs/source that pull from docstrings via autodoc.

Code Quality Test coverage is extensive rather than sparse: tests.py alone runs to over 100KB and pulls test vectors directly from RFC 7520’s worked JOSE examples, with tests-cookbook.py and tests_mldsa.py covering additional scenarios and the newer post-quantum algorithms as separate files. CI (tox-driven) runs the suite across Python 3.9 through 3.14 plus a dedicated ppc64le architecture job, and gates on pylint, flake8 (via pep8-naming and flake8-import-order), codespell, and Sphinx doctest/doc8 checks in addition to the tests themselves — a notably thorough lint/quality bar for a library this size. Type hints are minimal, though — the codebase relies on docstrings and runtime checks rather than static annotations, and typing_extensions is used only for a single deprecation decorator, not broader typed interfaces.

API Design The public API favors composition over convenience wrappers: JWK behaves like a plain dict so callers can inspect or serialize raw key material directly, while JWS and JWE expose an explicit sign/verify or encrypt/decrypt lifecycle. JWT sits on top and infers whether it’s dealing with a signed or encrypted token from the algs/header context, exposing header and claims as properties with validation (expiry, not-before, leeway, required claims) baked into deserialization rather than left to the caller. Getting started requires only a couple of calls — JWK.generate(kty=‘RSA’, size=2048), then JWS(payload) or JWT(claims=…, key=key) — with sensible defaults (60s leeway, 600s validity) that most consumers won’t need to touch. The tradeoff is a fairly deep custom exception hierarchy (InvalidJWSSignature, InvalidJWEData, JWTExpired, and more) that callers need to learn rather than a handful of generic exceptions.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search