JOSE protocol implementation in Python with support for JSON Web Algorithms, Keys, and Signatures
73
Key representation and management implementing the JSON Web Key specification (RFC 7517). Provides unified interfaces for RSA, Elliptic Curve, and symmetric keys with support for key loading, thumbprint generation, and public key extraction.
Abstract base class providing common functionality for all JSON Web Key types.
class JWK:
"""Base class for JSON Web Keys"""
def thumbprint(self, hash_function=hashes.SHA256) -> bytes:
"""
Compute JWK Thumbprint according to RFC 7638.
Parameters:
- hash_function: Hash algorithm factory (default: SHA256)
Returns:
bytes: Key thumbprint hash
"""
def public_key(self) -> 'JWK':
"""
Generate JWK containing only the public key.
For symmetric keys, returns self.
Returns:
JWK: Public key JWK instance
"""
@classmethod
def from_json(cls, jobj: Any) -> 'JWK':
"""Deserialize JWK from JSON object"""
def to_partial_json(self) -> Any:
"""Serialize JWK to JSON-compatible dictionary"""
@classmethod
def load(cls, data: bytes, password: Optional[bytes] = None, backend: Optional[Any] = None) -> 'JWK':
"""
Load serialized key as JWK with automatic type detection.
Parameters:
- data: Public or private key serialized as PEM or DER
- password: Optional password for encrypted private keys
- backend: Cryptography backend (defaults to default_backend())
Returns:
JWK: JWK instance of appropriate type (JWKRSA, JWKEC, or JWKOct)
Raises:
josepy.errors.Error: If unable to deserialize or unsupported algorithm
"""
@classmethod
def _load_cryptography_key(cls, data: bytes, password: Optional[bytes] = None, backend: Optional[Any] = None) -> Any:
"""Load cryptography key from PEM/DER data"""
key: Any # Underlying cryptography key objectRSA key representation supporting both private and public keys with PKCS#1 and PSS algorithms.
class JWKRSA(JWK):
"""RSA JSON Web Key"""
def __init__(self, key=None):
"""
Initialize RSA JWK.
Parameters:
- key: RSA private or public key from cryptography library,
or bytes/str containing PEM/DER encoded key data
"""
@classmethod
def load(cls, data: bytes, password: Optional[bytes] = None) -> 'JWKRSA':
"""
Load RSA key from PEM or DER encoded data.
Parameters:
- data: PEM or DER encoded key data
- password: Password for encrypted private keys
Returns:
JWKRSA: RSA JWK instance
"""
def public_key(self) -> 'JWKRSA':
"""Extract public key component"""
# JSON field mappings for RSA parameters
n: bytes # Modulus
e: bytes # Public exponent
d: bytes # Private exponent (private keys only)
p: bytes # First prime factor (private keys only)
q: bytes # Second prime factor (private keys only)
dp: bytes # First factor CRT exponent (private keys only)
dq: bytes # Second factor CRT exponent (private keys only)
qi: bytes # First CRT coefficient (private keys only)from josepy import JWKRSA
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
# Generate new RSA key
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# Create JWK from cryptography key
jwk = JWKRSA(key=private_key)
# Get public key JWK
public_jwk = jwk.public_key()
# Compute thumbprint
thumbprint = jwk.thumbprint()
print(f"Key thumbprint: {thumbprint.hex()}")
# Serialize to JSON
jwk_json = jwk.json_dumps()
print(f"JWK JSON: {jwk_json}")
# Load from JSON
loaded_jwk = JWKRSA.json_loads(jwk_json)
# Load from PEM data
pem_data = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
jwk_from_pem = JWKRSA.load(pem_data)Elliptic Curve key representation supporting NIST P-curves for ECDSA operations.
class JWKEC(JWK):
"""Elliptic Curve JSON Web Key"""
def __init__(self, key=None):
"""
Initialize EC JWK.
Parameters:
- key: EC private or public key from cryptography library,
or bytes/str containing PEM/DER encoded key data
"""
@classmethod
def load(cls, data: bytes, password: Optional[bytes] = None) -> 'JWKEC':
"""
Load EC key from PEM or DER encoded data.
Parameters:
- data: PEM or DER encoded key data
- password: Password for encrypted private keys
Returns:
JWKEC: EC JWK instance
"""
def public_key(self) -> 'JWKEC':
"""Extract public key component"""
# JSON field mappings for EC parameters
crv: str # Curve name ("P-256", "P-384", "P-521")
x: bytes # X coordinate
y: bytes # Y coordinate
d: bytes # Private key value (private keys only)from josepy import JWKEC
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
# Generate new EC key (P-256)
private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
# Create JWK from cryptography key
jwk_ec = JWKEC(key=private_key)
# Get public key JWK
public_jwk_ec = jwk_ec.public_key()
# Compute thumbprint
thumbprint_ec = jwk_ec.thumbprint()
print(f"EC Key thumbprint: {thumbprint_ec.hex()}")
# Different curves
p384_key = ec.generate_private_key(ec.SECP384R1(), default_backend())
jwk_p384 = JWKEC(key=p384_key)
p521_key = ec.generate_private_key(ec.SECP521R1(), default_backend())
jwk_p521 = JWKEC(key=p521_key)Octet string key representation for symmetric cryptographic operations including HMAC.
class JWKOct(JWK):
"""Symmetric (Octet String) JSON Web Key"""
def __init__(self, key=None):
"""
Initialize symmetric JWK.
Parameters:
- key: bytes containing the symmetric key material
"""
def public_key(self) -> 'JWKOct':
"""For symmetric keys, returns self"""
# JSON field mappings
k: bytes # Key value (base64url encoded in JSON)from josepy import JWKOct
import os
# Generate random symmetric key
key_bytes = os.urandom(32) # 256-bit key
jwk_oct = JWKOct(key=key_bytes)
# Symmetric keys are their own "public key"
same_key = jwk_oct.public_key()
assert jwk_oct is same_key
# Compute thumbprint
thumbprint_oct = jwk_oct.thumbprint()
print(f"Symmetric key thumbprint: {thumbprint_oct.hex()}")
# Use with HMAC algorithms
from josepy import HS256
message = b"Hello, HMAC!"
signature = HS256.sign(jwk_oct.key, message)
is_valid = HS256.verify(jwk_oct.key, message, signature)
print(f"HMAC signature valid: {is_valid}")from josepy import JWKRSA, JWKEC
# Load from PEM files
with open('private_key.pem', 'rb') as f:
pem_data = f.read()
jwk_rsa = JWKRSA.load(pem_data)
# Load password-protected keys
with open('encrypted_key.pem', 'rb') as f:
encrypted_pem = f.read()
jwk_rsa = JWKRSA.load(encrypted_pem, password=b'secret')
# Load from DER format
with open('key.der', 'rb') as f:
der_data = f.read()
jwk_rsa = JWKRSA.load(der_data)
# Load EC keys
with open('ec_key.pem', 'rb') as f:
ec_pem = f.read()
jwk_ec = JWKEC.load(ec_pem)from josepy import JWKRSA, JWKEC, JWKOct
# Serialize any JWK to JSON
jwk_json = jwk.json_dumps(indent=2)
# Deserialize from JSON (automatic type detection)
from josepy import JWK
loaded_jwk = JWK.from_json(json.loads(jwk_json))
# Type-specific deserialization
rsa_jwk = JWKRSA.json_loads(jwk_json)
ec_jwk = JWKEC.json_loads(jwk_json)
oct_jwk = JWKOct.json_loads(jwk_json)JWK thumbprints provide unique identifiers for keys according to RFC 7638:
from josepy import JWKRSA
from cryptography.hazmat.primitives import hashes
# Default SHA-256 thumbprint
thumbprint = jwk.thumbprint()
# Custom hash function
thumbprint_sha1 = jwk.thumbprint(hashes.SHA1)
thumbprint_sha512 = jwk.thumbprint(hashes.SHA512)
# Thumbprints are deterministic and unique per key
assert jwk.thumbprint() == jwk.thumbprint()
assert public_jwk.thumbprint() == jwk.thumbprint() # Same for public keyfrom josepy.errors import DeserializationError
try:
# Invalid JSON structure
jwk = JWKRSA.json_loads('{"invalid": "json"}')
except DeserializationError as e:
print(f"Deserialization failed: {e}")
try:
# Invalid key data
jwk = JWKRSA.load(b"not a valid key")
except (ValueError, TypeError) as e:
print(f"Key loading failed: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-josepyevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10