import json from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsafrom cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, load_pem_public_keyfrom jwt.algorithms import ECAlgorithm, OKPAlgorithm, RSAAlgorithm # PyJWT, with "pyjwt[crypto]" def pem_to_jwk(pem: bytes) -> dict: key = load_pem_public_key(pem) if isinstance(key, ec.EllipticCurvePublicKey): return ECAlgorithm.to_jwk(key, as_dict=True) if isinstance(key, rsa.RSAPublicKey): return RSAAlgorithm.to_jwk(key, as_dict=True) if isinstance(key, ed25519.Ed25519PublicKey): return OKPAlgorithm.to_jwk(key, as_dict=True) raise TypeError(f"unsupported key type {type(key).__name__}") def jwk_to_pem(jwk: dict) -> bytes: algorithm = {"EC": ECAlgorithm, "RSA": RSAAlgorithm, "OKP": OKPAlgorithm}[jwk["kty"]] key = algorithm.from_jwk(jwk) # rejects points that are not on the curve return key.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)with open("pub.pem", "rb") as f: jwk = pem_to_jwk(f.read())print(json.dumps(jwk))with open("out.jwk.json", "w") as f: json.dump(jwk, f) with open("pub.jwk.json") as f: pem = jwk_to_pem(json.load(f))print(pem.decode(), end="")with open("out.pem", "wb") as f: f.write(pem) with open("ed25519.pem", "rb") as f: print(json.dumps(pem_to_jwk(f.read())))How it works
load_pem_public_keyreturns a typed key object; theisinstancechecks pick the matching algorithm class.to_jwk(key, as_dict=True)returns a dict; withoutas_dictit returns a JSON string.from_jwk(jwk)rebuilds the key and validates it: an EC point off the curve raisesValueError: Invalid EC key. Point is not on the curve specified.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)writes the sameBEGIN PUBLIC KEYPEM the file started as.
Gotchas
to_jwkof a private key includesd: passprivate_key.public_key()when the JWK will be published.RSAAlgorithm.to_jwkadds"key_ops": ["verify"]to public keys (["sign"]to private ones). Most consumers ignore it; strict ones that checkkey_opsagainst the intended use may not.- The JWK carries no
kid,useoralg— add them before publishing; PyJWT'sPyJWKClientand most verifiers select keys bykid. - These classes need the cryptography package (
pyjwt[crypto]); without it,jwt.algorithmshas noECAlgorithm, and the import fails.