JWKS viewer / JWK ↔ PEM converter
Save on GoogleFree JWKS viewer and JWK to PEM converter. Inspect keys, check them for mistakes, and convert between JWK and PEM. Everything runs in your browser. Nothing is uploaded.
Configure keys
Results & Output
Keys
The tool runs in your browser. Do not share links or files that contain private keys.
Passkeys, OAuth and Identity
| Tool | What it does |
|---|---|
| WebAuthn options JSON builder / inspector | Build WebAuthn registration or sign-in options, or paste existing JSON to check it. It catches mistakes such as a padded challenge, a wrong rp.id, or an email used as the user ID, so your passkey setup works the first time. |
| PKCE code verifier / challenge generator | Create a PKCE code verifier and its S256 code challenge in your browser, check values you already have, and get the authorization and token requests ready to use. Handy when a login fails with invalid_grant. |
| OIDC discovery document validator | Paste the JSON from /.well-known/openid-configuration and see which required fields are missing, which URLs are wrong, and whether the issuer matches. The results are color-coded, so you spot problems fast. |
| OAuth redirect URI and scope checker | Compare the redirect URI in your request with the ones registered for your app, and check your scopes for mistakes. It helps you fix redirect_uri_mismatch and invalid_scope errors. |
What does a JWKS viewer and JWK to PEM converter do?
A JSON Web Key Set lists the public keys that applications use to verify tokens, while servers and tools such as OpenSSL usually expect PEM files. This tool shows what is inside a JWKS (key type, size or curve, algorithm, thumbprint and whether a key is private), checks every key for common mistakes, and converts in both directions between JWK and PEM. RSA keys are checked for size, EC keys are checked to be on their curve, and any private data is flagged in red. It does not read certificates or encrypted keys.
JWK and PEM 100+ developer references
Click Copy to copy a snippet. Snippets are examples, so replace placeholders such as auth.example.com and file names with your own values. Library APIs change, so check the documentation of your version.
| Developer Question | Copy-ready Snippet | Explanation |
|---|---|---|
| ConceptWhat is a JWK? | { "kty": "RSA", "n": "...", "e": "AQAB", "kid": "key-1" } | A JSON Web Key (RFC 7517) is a JSON representation of a cryptographic key. |
| ConceptWhat is a JWKS? | { "keys": [{ "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }] } | A JWK Set is a JSON object with a keys array. Providers publish one so clients can verify tokens. |
| How-toWhere do I find a provider’s JWKS? | curl -s https://auth.example.com/.well-known/openid-configuration | jq -r .jwks_uri | Read jwks_uri from the OpenID discovery document. |
| FieldWhat is kty? | "kty": "RSA" | The key type: RSA, EC, OKP or oct. |
| FieldWhat is kid? | "kid": "2024-key-1" | A key ID. A token header carries a kid so the verifier can pick the matching key. |
| FieldWhat is use? | "use": "sig" | sig means signing and verification. enc means encryption. |
| FieldWhat is alg in a JWK? | "alg": "RS256" | The algorithm the key is meant for. |
| FieldWhat is key_ops? | "key_ops": ["verify"] | An optional list of allowed operations. Do not combine it with a conflicting use. |
| ConceptWhat is a JWK thumbprint? | {"e":"AQAB","kty":"RSA","n":"..."} | RFC 7638 defines it as the SHA-256 hash of the required members in a fixed order, as base64url. It is a stable key ID. |
| FormatWhat is PEM? | -----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY----- | A text format: base64 of DER data between BEGIN and END lines. |
| FormatPEM vs JWK | // PEM: base64 DER text JWK: JSON members | Both describe the same key. JWK is JSON for web protocols, and PEM is common in OpenSSL and servers. |
| FormatWhat is SPKI? | -----BEGIN PUBLIC KEY----- | SubjectPublicKeyInfo is the standard public key container. Its PEM label is PUBLIC KEY. |
| FormatWhat is PKCS#8? | -----BEGIN PRIVATE KEY----- | A standard container for private keys of any type. Its PEM label is PRIVATE KEY. |
| FormatWhat is PKCS#1? | -----BEGIN RSA PRIVATE KEY-----
-----BEGIN RSA PUBLIC KEY----- | The older RSA-only formats. Their labels contain the word RSA. |
| FormatWhat is EC PRIVATE KEY (SEC1)? | -----BEGIN EC PRIVATE KEY----- | The older EC private key format that openssl ecparam produces. |
| Formatbase64url vs base64 | btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") | JWK values use base64url without padding. PEM uses standard base64 with padding. |
| FieldRSA modulus n | "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmb..." | The RSA modulus as base64url. |
| FieldRSA exponent e | "e": "AQAB" | The public exponent. AQAB is 65537, the usual value. |
| SecurityRSA private exponent d | "d": "..." | Its presence means the JWK holds a private key. |
| SecurityRSA p, q, dp, dq, qi | "p", "q", "dp", "dq", "qi" | More private key values. They are never part of a public key. |
| FieldEC crv | "crv": "P-256" | The curve: P-256, P-384 or P-521. |
| FieldEC x and y | "x": "...", "y": "..." | The public point coordinates as base64url, with the full length of the curve. |
| SecurityEC private value d | "d": "..." | The EC private key value. Never publish it. |
| FieldOKP Ed25519 public key | { "kty": "OKP", "crv": "Ed25519", "x": "..." } | An Edwards-curve public key, used with the EdDSA algorithm. |
| Securityoct (symmetric) key | { "kty": "oct", "k": "..." } | A shared secret. Never publish a JWKS that contains k. |
| FieldCoordinate size for P-256 | x and y: 32 bytes = 43 base64url characters | Both coordinates must have the full length of the curve. |
| FieldCoordinate size for P-384 | x and y: 48 bytes = 64 base64url characters | Both coordinates must have the full length of the curve. |
| FieldCoordinate size for P-521 | x and y: 66 bytes = 88 base64url characters | Both coordinates must have the full length of the curve. |
| FieldWhich alg fits which key? | RS256, PS256 -> RSA ES256 -> P-256 ES384 -> P-384 ES512 -> P-521 EdDSA -> OKP | A mismatch makes verification fail. The validator flags it. |
| FieldRS256 vs PS256 | "alg": "PS256" | Both use an RSA key. RS256 uses PKCS#1 v1.5 padding and PS256 uses PSS padding. |
| ErrorES512 uses P-521 | "alg": "ES512", "crv": "P-521" | Despite the 512 in the name, ES512 uses the P-521 curve. |
| SecurityMinimum RSA key size | openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 | Use at least 2048 bits. The tool warns about smaller keys. |
| How-toJWKS with several keys | { "keys": [{ "kid": "new-key", ... }, { "kid": "old-key", ... }] } | List the new and the old key during a rotation, each with a unique kid. |
| How-toGenerate an RSA private key | openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem | Creates a 2048-bit key in PKCS#8 format. |
| How-toExtract the public key from a private key | openssl pkey -in private.pem -pubout -out public.pem | Writes a PUBLIC KEY PEM. |
| How-toGenerate an EC P-256 key (SEC1) | openssl ecparam -name prime256v1 -genkey -noout -out ec-private.pem | Creates an EC PRIVATE KEY PEM. |
| How-toGenerate an EC P-256 key (PKCS#8) | openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec-private.pem | Creates a PRIVATE KEY PEM. |
| How-toGenerate an Ed25519 key | openssl genpkey -algorithm ED25519 -out ed25519-private.pem | Creates a PKCS#8 private key. |
| How-toConvert PKCS#1 to PKCS#8 | openssl pkcs8 -topk8 -nocrypt -in rsa-private.pem -out pkcs8-private.pem | Turns RSA PRIVATE KEY into PRIVATE KEY. |
| How-toConvert PKCS#8 to PKCS#1 (RSA) | openssl rsa -in pkcs8-private.pem -traditional -out pkcs1-private.pem | Needs OpenSSL 3. Turns PRIVATE KEY into RSA PRIVATE KEY. |
| How-toShow key details | openssl pkey -in key.pem -text -noout | Prints the key parameters in readable form. |
| How-toGet the public key from a certificate | openssl x509 -in cert.pem -pubkey -noout | Prints a PUBLIC KEY PEM that you can paste into the tool. |
| How-toShow the RSA modulus of a public key | openssl rsa -pubin -in public.pem -modulus -noout | Prints the modulus in hex. |
| How-toConvert DER to PEM | openssl pkey -inform DER -in key.der -out key.pem | Use -pubin for a DER public key. |
| How-toConvert PEM to DER | openssl pkey -in key.pem -outform DER -out key.der | Writes the binary DER form. |
| How-toDecrypt an encrypted private key | openssl pkey -in encrypted.pem -out decrypted.pem | It asks for the passphrase. This tool never reads encrypted keys. |
| How-toCertificate SHA-256 fingerprint | openssl x509 -in cert.pem -noout -fingerprint -sha256 | A fingerprint of the certificate, not the same as a JWK thumbprint. |
| How-toCheck that a private and a public key match | diff <(openssl pkey -in private.pem -pubout) public.pem | Needs a shell that supports process substitution, such as bash. |
| How-toNode.js: JWK to a key object | const key = crypto.createPublicKey({ key: jwk, format: "jwk" }); | Works for public and private JWKs. |
| How-toNode.js: export a PEM from a key | const pem = key.export({ type: "spki", format: "pem" }); | Use type "pkcs8" for a private key. |
| How-toNode.js: PEM to JWK | const jwk = crypto.createPublicKey(pem).export({ format: "jwk" }); | Returns the JWK members as an object. |
| How-toNode.js: export a private PEM (PKCS#8) | const pem = privateKey.export({ type: "pkcs8", format: "pem" }); | Keep the result secret. |
| How-toNode.js: generate an EC key pair | const { publicKey, privateKey } = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" }); | Use "rsa" with modulusLength for RSA keys. |
| How-toNode.js: JWK thumbprint of an RSA key | crypto.createHash("sha256").update(JSON.stringify({ e: jwk.e, kty: jwk.kty, n: jwk.n })).digest("base64url") | The members must stay in this order. EC keys use crv, kty, x and y. |
| How-tojose: import a JWK | const key = await importJWK(jwk, "RS256"); | Imports a JWK as a key you can verify with. |
| How-tojose: export a PEM | const pem = await exportSPKI(publicKey); | Use exportPKCS8 for a private key. |
| How-tojose: PEM to JWK | const key = await importSPKI(pem, "RS256");
const jwk = await exportJWK(key); | Imports the PEM public key, then exports it as a JWK. |
| How-tojose: thumbprint | const kid = await calculateJwkThumbprint(jwk); | Returns the RFC 7638 thumbprint. |
| How-tojose: remote JWKS | const JWKS = createRemoteJWKSet(new URL("https://auth.example.com/.well-known/jwks.json")); | Fetches and caches the key set. |
| How-tojose: verify a token with a JWKS | const { payload } = await jwtVerify(token, JWKS, { issuer, audience }); | Checks the signature, issuer and audience. |
| How-toBrowser: import an RSA JWK | const key = await crypto.subtle.importKey("jwk", jwk, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, true, ["verify"]); | Use RSA-PSS for PS256 keys. |
| How-toBrowser: import an EC JWK | const key = await crypto.subtle.importKey("jwk", jwk, { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]); | Match the curve to the key. |
| How-toBrowser: export SPKI | const spki = await crypto.subtle.exportKey("spki", key); | Returns an ArrayBuffer. Wrap its base64 in PEM lines. |
| How-toBrowser: export a JWK | const jwk = await crypto.subtle.exportKey("jwk", key); | The key must have been imported as extractable. |
| How-toBrowser: base64 from an ArrayBuffer | const b64 = btoa(String.fromCharCode(...new Uint8Array(spki))); | Add -----BEGIN PUBLIC KEY----- lines and a line break every 64 characters. |
| How-toPython cryptography: load a PEM public key | from cryptography.hazmat.primitives import serialization
key = serialization.load_pem_public_key(pem_bytes) | Returns a public key object. |
| How-toPython jwcrypto: PEM to JWK | from jwcrypto import jwk
key = jwk.JWK.from_pem(pem_bytes)
print(key.export_public()) | Prints the public JWK as JSON. |
| How-toPython jwcrypto: JWK to PEM | pem = key.export_to_pem() | Add private_key=True to export the private key. |
| How-toPyJWT: RSA key from a JWK | import json, jwt
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk)) | Returns a key you can pass to jwt.decode. |
| How-toPyJWT: EC key from a JWK | public_key = jwt.algorithms.ECAlgorithm.from_jwk(json.dumps(jwk)) | Use it for ES256, ES384 and ES512 tokens. |
| How-toPyJWT: JWKS client | client = jwt.PyJWKClient("https://auth.example.com/.well-known/jwks.json")
signing_key = client.get_signing_key_from_jwt(token) | Fetches the JWKS and picks the key by kid. |
| How-toJava Nimbus: parse a PEM | JWK jwk = JWK.parseFromPEMEncodedObjects(pem); | From the Nimbus JOSE + JWT library. Check your version. |
| How-toJava Nimbus: load a JWKS | JWKSet set = JWKSet.load(new URL(jwksUri)); | Loads the key set from a URL. Newer versions offer other loaders. |
| How-toPHP firebase/php-jwt | $keys = JWK::parseKeySet(json_decode($jwksJson, true)); | Returns keys indexed by kid. |
| How-toRuby ruby-jwt | public_key = JWT::JWK.import(jwk_hash).public_key | Imports a JWK hash and returns the OpenSSL key. |
| How-to.NET JsonWebKeySet | var jwks = new JsonWebKeySet(json);
var key = jwks.Keys.First(k => k.Kid == kid); | From Microsoft.IdentityModel.Tokens. |
| How-toGo jwx library | go get github.com/lestrrat-go/jwx/v2/jwk | A Go library for JWK handling. Check the docs of your version. |
| How-toList the kids with jq | curl -s https://auth.example.com/.well-known/jwks.json | jq -r '.keys[].kid' | Prints one kid per line. |
| How-toSelect one key by kid | jq '.keys[] | select(.kid == "key-1")' jwks.json | Prints the matching key. |
| How-toCount the keys | jq '.keys | length' jwks.json | Prints the number of keys. |
| How-toList kid, kty and alg | jq -r '.keys[] | "\(.kid) \(.kty) \(.alg)"' jwks.json | A compact overview of all keys. |
| SecurityRemove private fields from a JWK | jq 'del(.d, .p, .q, .dp, .dq, .qi)' private-jwk.json | Leaves only the public members. |
| How-toFetch a JWKS in JavaScript | const jwks = await fetch(jwksUri).then((r) => r.json()); | Works in browsers (with CORS) and in Node.js 18 or later. |
| How-toHow often should I refresh a JWKS? | Cache-Control: public, max-age=3600 | Follow the server’s caching headers, and refetch once when you see an unknown kid. |
| SecurityRotate signing keys | // publish new + old key, sign with the new key, remove the old one after its tokens expire | Overlap the keys so tokens signed with the old key stay valid until they expire. |
| How-toMatch a token to a key | const key = jwks.keys.find((k) => k.kid === header.kid); | Use the kid from the token header. |
| How-toToken header example | { "alg": "RS256", "typ": "JWT", "kid": "key-1" } | The kid points to a key in the JWKS. |
| ErrorNo matching key for kid | const key = jwks.keys.find((k) => k.kid === header.kid); | Refetch the JWKS once, and check that the token comes from the right issuer. |
| ErrorPEM routines: no start line | -----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY----- | The text is not valid PEM. Check the BEGIN and END lines and the line breaks. |
| ErrorPEM with Windows line endings | tr -d '\r' < key.pem > key-unix.pem | CRLF line endings can break some parsers. This tool accepts both. |
| ErrorUnknown key type error | "kty": "RSA" | Check kty. Only RSA, EC, OKP and oct are defined for JWK. |
| ErrorSignature verification failed | // check kid, alg, key type and issuer | Typical causes: the wrong key, the wrong alg, a key from another tenant, or a changed token. |
| ErrorInvalid base64url in a JWK | "n": "0vx7ago..." // no "=", "+" or "/" | Remove padding, and replace + and / with - and _. |
| ErrorEC point is not on the curve | // x and y do not satisfy the curve equation | The key is corrupt, or the coordinates are swapped or cut short. The tool checks this. |
| ErrorWrong PEM label | -----BEGIN RSA PRIVATE KEY----- vs -----BEGIN PRIVATE KEY----- | The label tells which DER format is inside. Do not change a label by hand. |
| ErrorEncrypted private key | -----BEGIN ENCRYPTED PRIVATE KEY----- | Decrypt it first with openssl. The tool never asks for a passphrase. |
| ErrorJWKS with an empty keys array | { "keys": [] } | Verification always fails. Publish at least one key. |
| ErrorDuplicate kid values | "kid": "key-1" // used twice | A verifier cannot tell which key to use. Give every key a unique kid. |
| SecurityPrivate key in a public JWKS | "d": "..." // never publish | If you published one, treat the key as compromised and rotate it. |
| SecurityPublish only public keys | jq 'del(.d, .p, .q, .dp, .dq, .qi)' | Strip private members before you publish. Turn on Public keys only in the tool. |
| SecurityDo not use a public key as an HMAC secret | // accept only the algorithms you expect | In an algorithm confusion attack, a forged HS256 token is verified with the public key. Pin the allowed algorithms. |
| SecurityDo not trust jku or x5u headers from tokens | // ignore jku and x5u unless you allow-list the URL | A token can point to a key set that an attacker controls. |
| SecurityFetch a JWKS only from trusted URLs | const JWKS_URI = "https://auth.example.com/.well-known/jwks.json"; | Take the URL from your configured issuer’s discovery document. |
| SecurityStore private keys safely | // use a KMS or HSM, not source control | Never commit private keys to a repository. |
| SecurityAvoid pasting production private keys anywhere | // generate a test key instead | This tool runs in your browser, but a test key is safer for experiments. |
FAQ
| Question | Answer |
|---|---|
| What is a JWKS? | A JWK Set (JWKS) is a JSON object with a keys array of JSON Web Keys (RFC 7517). Identity providers publish one at their jwks_uri so applications can verify the signatures of tokens. |
| How do I convert a JWK to PEM? | Paste a JWK or a JWKS into the tool. It converts RSA, EC (P-256, P-384, P-521) and Ed25519 or X25519 keys into PEM. Public keys become PUBLIC KEY blocks (SPKI). If you turn off Public keys only and the input has a private key, you also get a PRIVATE KEY block (PKCS#8). |
| How do I convert a PEM key to a JWK? | Paste the PEM into the tool. It reads PUBLIC KEY, RSA PUBLIC KEY, PRIVATE KEY, RSA PRIVATE KEY and EC PRIVATE KEY blocks and returns a JWKS. You can add a kid from the key’s RFC 7638 thumbprint, and add use and alg values. |
| What is a JWK thumbprint and why use it as a kid? | A JWK thumbprint (RFC 7638) is the base64url SHA-256 hash of the key’s required members written as JSON in a fixed order. It always gives the same value for the same key, so it works well as a stable kid. |
| Why does the tool report an error for a private key in my JWKS? | A public JWKS must never contain private members such as d, p, q, dp, dq, qi or k. If you already published a private key, treat it as compromised and rotate it. |
| Is it safe to paste my keys here? | The conversion runs entirely in your browser and nothing is uploaded. Still, avoid pasting production private keys into any web page. The Share link button puts your input in the URL, so never share a link that contains a private key. |