Skip to content

Instantly share code, notes, and snippets.

@akrabat
Created August 19, 2025 11:24
Show Gist options
  • Save akrabat/b69c23ab935caa5cd27f257aa0d775f5 to your computer and use it in GitHub Desktop.
Save akrabat/b69c23ab935caa5cd27f257aa0d775f5 to your computer and use it in GitHub Desktop.

Revisions

  1. akrabat created this gist Aug 19, 2025.
    91 changes: 91 additions & 0 deletions jwk-to-pem.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,91 @@
    #!/usr/bin/env -S uv run --script --quiet
    # /// script
    # dependencies = [
    # "cryptography",
    # ]
    # ///

    """Convert JWK keys to PEM format.
    This script reads .well-known/jwks.json and outputs PEM encoded versions
    of the public keys in that file.
    This file should be set as executable (chmod +x jwk-to-pem.py).
    Usage:
    curl -s https://example.com/.well-known/jwks.json | jwk-to-pem.py
    uv run jwk-to-pem.py jwks.json
    uv run jwk-to-pem.py < jwks.json
    Requirements:
    - uv (https://github.com/astral-sh/uv)
    - cryptography library
    Author:
    Rob Allen <[email protected]>
    Copyright 2025
    License:
    MIT License - https://opensource.org/licenses/MIT
    """

    import json
    import base64
    import sys
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.primitives.asymmetric import rsa

    def base64url_decode(data):
    """Decode base64url to bytes"""
    # Add padding if needed
    padding = 4 - len(data) % 4
    if padding != 4:
    data += '=' * padding

    # Replace URL-safe chars
    data = data.replace('-', '+').replace('_', '/')

    # Decode
    return base64.b64decode(data)

    def jwk_to_pem(jwk_key):
    """Convert JWK to PEM format"""
    if jwk_key['kty'] != 'RSA':
    raise ValueError("Only RSA keys are supported")

    # Decode the modulus (n) and exponent (e) to int
    n = int.from_bytes(base64url_decode(jwk_key['n']), 'big')
    e = int.from_bytes(base64url_decode(jwk_key['e']), 'big')

    # Create RSA public key
    public_key = rsa.RSAPublicNumbers(e, n).public_key()

    # Serialize to PEM
    pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
    )
    return pem.decode()

    def main():
    if len(sys.argv) > 2:
    print("Usage: jwk_to_pem.py [jwks.json]")
    print("If no file is provided, reads from stdin")
    sys.exit(1)

    if len(sys.argv) == 2 and sys.argv[1] != '-':
    # Read from file
    with open(sys.argv[1], 'r') as f:
    jwks = json.load(f)
    else:
    # Read from stdin
    jwks = json.load(sys.stdin)

    # Convert each key
    for i, key in enumerate(jwks['keys']):
    kid = key.get('kid', f'key-{i}')
    print(f"# Key {i} (kid: {kid})")
    print(jwk_to_pem(key))

    if __name__ == "__main__":
    main()