Skip to content
ecdsa.com

PEM ⇄ JWK · Python

How to convert a PEM public key to a JWK and back in Python

In Python, PyJWT's algorithm classes convert keys both ways: ECAlgorithm, RSAAlgorithm and OKPAlgorithm have to_jwk for cryptography key objects loaded from PEM, and from_jwk to get a key back, which public_bytes writes as SPKI PEM. The run checks the RFC 7515 P-256 key round trip byte for byte and converts an Ed25519 key too.

Tested with PyJWT 2.15.1 (Python 3.14.8, cryptography 50.0.2).

pem_to_jwk and jwk_to_pem for EC, RSA and Ed25519 keys
import json
from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsa
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, load_pem_public_key
from 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)
convert pub.pem, convert pub.jwk.json back, and convert an Ed25519 key
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

  1. load_pem_public_key returns a typed key object; the isinstance checks pick the matching algorithm class.
  2. to_jwk(key, as_dict=True) returns a dict; without as_dict it returns a JSON string.
  3. from_jwk(jwk) rebuilds the key and validates it: an EC point off the curve raises ValueError: Invalid EC key. Point is not on the curve specified.
  4. public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo) writes the same BEGIN PUBLIC KEY PEM the file started as.

Gotchas

  • to_jwk of a private key includes d: pass private_key.public_key() when the JWK will be published.
  • RSAAlgorithm.to_jwk adds "key_ops": ["verify"] to public keys (["sign"] to private ones). Most consumers ignore it; strict ones that check key_ops against the intended use may not.
  • The JWK carries no kid, use or alg — add them before publishing; PyJWT's PyJWKClient and most verifiers select keys by kid.
  • These classes need the cryptography package (pyjwt[crypto]); without it, jwt.algorithms has no ECAlgorithm, and the import fails.

Related recipes