JOSE Protocol Implementation in Python
josepy is a Python library that implements the JOSE (JSON Object Signing and Encryption) protocol, providing cryptographic primitives for creating and verifying JWS (JSON Web Signatures) and JWE (JSON Web Encryption) messages. It serves as a foundational cryptography component for projects like Certbot. The current version is 2.2.0, with releases typically aligned with Certbot updates or critical security fixes for its dependencies.
Warnings
- breaking As of josepy 2.2.0, Python versions older than 3.9.2 are no longer supported. Ensure your environment meets this minimum requirement.
- gotcha josepy relies on the `cryptography` library for its core cryptographic operations. Updates to `cryptography` can sometimes introduce breaking changes or require specific versions. While josepy abstracts much of this, ensure `cryptography` and `josepy` versions are compatible, especially when dealing with new features or security patches.
- gotcha Choosing weak or deprecated algorithms (e.g., 'none' for `alg`) can lead to severe security vulnerabilities. josepy provides the flexibility to use various algorithms, but it is the user's responsibility to select cryptographically strong and appropriate ones for their use case.
- gotcha Secure management of private keys is paramount. josepy provides the cryptographic primitives, but it does not dictate how keys should be stored or accessed. Storing private keys insecurely (e.g., directly in code, unencrypted on disk) can lead to full system compromise.
Install
-
pip install josepy
Imports
- JWSMessage
from josepy.jws import JWSMessage
- RS256
from josepy.jwa import RS256
- JWK
from josepy.jwk import JWK
- Protected
from josepy.jws import Protected
Quickstart
import os
from josepy import jwa, jws
from josepy.jwk import JWK
# 1. Generate or load a private key for signing
# In a production environment, load keys securely (e.g., from environment variables, KMS, or disk).
key_pem = os.environ.get('JOSEPY_PRIVATE_PRIVATE_KEY_PEM', None)
if key_pem:
private_key = JWK.load(key_pem.encode('utf-8'))
else:
# Generate a new RSA key for demonstration purposes
private_key = jwa.RS256.create().key
print("Generated a new RSA private key for demonstration. In production, load securely.")
# 2. Define the payload and protected header
payload = b"Hello, josepy! This is a signed message."
protected_header = jws.Protected({"alg": "RS256", "jwk": private_key.public_key.json_serializable})
# 3. Sign the message
signer = jwa.RS256.create(key=private_key)
jws_msg = jws.JWSMessage.sign(
payload=payload,
alg=jwa.RS256,
key=private_key,
protected=protected_header,
)
serialized_jws = jws_msg.json_dumps(indent=2).decode('utf-8')
print(f"\nSigned JWS:\n{serialized_jws}")
# 4. Verify the message
# The verifier typically has the public key corresponding to the signer's private key.
parsed_jws = jws.JWSMessage.json_loads(serialized_jws.encode('utf-8'))
try:
verified_payload = parsed_jws.verify(
verifier_key=private_key.public_key, # Use the public key for verification
alg=jwa.RS256
)
print(f"\nVerification successful! Payload: {verified_payload.decode('utf-8')}")
assert verified_payload == payload
except Exception as e:
print(f"\nVerification failed: {e}")