PKCE Generator
Mint a PKCE code_verifier and its S256 code_challenge on this device — RFC 7636 lengths, and a verify mode for a pair you already have.
One-off state and nonce values: API Key GeneratorRead or check a JWT: JWT Decoder
Nothing generated yet.
Download writes this code_verifier to your disk as a plain-text file.
Not stored between visits — this input looks credential-shaped.
Where each value goes
- Authorization request
GET /authorizecode_challengecode_challenge_method - Token request
POST /tokencode_verifier
The challenge travels with the authorization request; the verifier is held back until the token request.
Press ? for shortcuts · The verifier is drawn in memory and never written to storage or sent anywhere.
This page loads no third-party code — no ads, no trackers, no analytics. Verify in your Network tab.
Do it offline:$ printf "%s" "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '='
How to use it
- Pick the draw shape: 32 to 96 random bytes rendered as base64url, or 43 to 128 characters taken uniformly from the unreserved set.
- Leave the method on S256. plain exists only to reproduce a legacy client, and every plain result carries the RFC 7636 §4.2 note explaining why.
- Press Generate, or ⌘/Ctrl + Enter, then copy the verifier and the challenge separately or both together.
- Send code_challenge and code_challenge_method with the authorization request; hold the verifier back and send it with the token request.
- To debug a failing exchange, open Verify, paste the verifier and the challenge, and read the verdict beside the challenge this page derived.
Draw a code_verifier, hash it into its S256 code_challenge, and copy each one where it belongs — or switch to Verify and check a pair you already have. Lengths follow RFC 7636 §4.1: 43 to 128 characters from the unreserved set, with the challenge written in base64url and never padded. Both values are drawn on this device, and neither is stored nor sent.
How it works
S256 is one line of arithmetic. Every character of the unreserved set is a single ASCII byte, so the verifier string and the octet sequence RFC 7636 §4.2 hashes are the same run of bytes; those bytes go through SHA-256, and the 32-byte digest is written in the URL-safe alphabet of RFC 4648 §5 with the padding removed.
That is where 43 comes from. The digest is 256 bits, base64url spends 6 bits per character, and 256 ÷ 6 is 42 with 4 bits over, so the last character is partly filled and the total is always 43 — never 44, and never with an `=` on the end.
The two draw shapes price entropy differently. Whole bytes are worth 8 bits each, so 32 bytes are 256 bits and encode to 43 characters while 96 bytes are 768 bits and encode to 128 — the RFC's own bounds. Drawing characters instead takes each symbol uniformly from the 66 in the set, worth log₂ 66 = 6.0444 bits, so 43 of them carry 259.91 bits and 64 carry 386.84.
Examples
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkE9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cMThe Appendix B vector: the RFC's 32 example bytes encode to that verifier, and its 43 ASCII bytes hash to that challenge.
generate · 32 bytes · S256code_verifier: DiLId_zjgpyruLx2DKfWJpKOIWIp2zG9Kc-M-cUL6W8
code_challenge: v39zBpN7-tgS4AUzWr_LIxyXe6wsDfQFZ5YCtcqgm94The default: 32 bytes are 256 bits and land on exactly 43 characters. Yours will differ — the draw is random — but the second line is always the SHA-256 of the first.
generate · 43 characters · unreserved setcode_verifier: nbfmgUB4ZIr~-JdnzLBxAxkqhj2Lsf_NILVXUyb7OML
code_challenge: rqZrUPEHdutoXTQTsOb0e4kbVzhFUzgfD1QWYt9fKUkThe character draw at the same length: 259.91 bits rather than 256, and `~` is as legal here as any letter. Random output, checked by shape.
verify · challenge pasted with = paddingmatch · challenge derived here: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cMA library that padded its output is still a match: the trailing `=` is dropped with a note, not counted as a difference.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Both ends of the band are legal
43 and 128 characters are each accepted; 42 and 129 are refused with both bounds printed, never quietly padded or cut.
`~` and `.` belong to the set
The unreserved set is A–Z, a–z, 0–9 and the four punctuation marks `-`, `.`, `_`, `~` — 66 symbols. RFC 7636 §4.1 lists all four, so `.` and `~` are as legal in a verifier as any letter.
One verifier per authorization request
A verifier is spent when its code is redeemed. Reusing the pair lets anyone who saw the first challenge finish the next attempt.
Padding is tolerated, the wrong alphabet is not
A trailing `=` on a pasted challenge is stripped and noted. A `+` or `/` is standard Base64 (RFC 4648 §4), not the URL-safe alphabet S256 uses, and is called out by position instead.
Nothing outlives the tab
No verifier, challenge or preference is written to this browser, and a shared link carries the draw settings alone.
Common errors
RFC 7636 §4.1 makes a code_verifier 43 to 128 characters — this one is 42 characters. Adjust it to fall inside those bounds.- The band is the spec's, not this page's; a value outside it is refused rather than trimmed.
The octet draw takes a whole number of octets from 32 to 96 — 16 is outside that range. 32 octets encode to 43 base64url characters and 96 octets to 128, which are the RFC 7636 §4.1 bounds.- Counts outside the range refuse before anything is drawn.
code_verifier: `+` at position 1 is not in the RFC 7636 §4.1 unreserved set (A-Z a-z 0-9 - . _ ~). `+` and `/` belong to standard Base64 (RFC 4648 §4), not to this set.- Usually a verifier encoded with the standard alphabet upstream; each offending position is listed separately.
code_challenge: `+` at position 41 is not a base64url character (RFC 4648 §5: A-Z a-z 0-9 - _). `+` and `/` come from standard Base64 (RFC 4648 §4); S256 uses the URL-safe alphabet, where they are `-` and `_`.- The same mix-up on the challenge side: swap `+` for `-` and `/` for `_`.
An S256 code_challenge is always 43 characters — base64url of a 32-byte SHA-256 digest, unpadded — but this one is 3 characters.- A fixed-size digest has a fixed rendering, so any other length came from somewhere else.
There is no code_verifier here yet — paste one, or generate one above. RFC 7636 §4.1 makes it 43 to 128 characters from the unreserved set.- Whitespace alone counts as empty; whitespace around a real value is trimmed with a note.
Input is larger than the 4 KB limit for this tool. A code_verifier is at most 128 characters, so this is not one; to hash something bigger, use the command line: printf "%s" "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '='- A paste that large is a file, not a verifier, so the page hands over the command instead.
FAQ
Can I reuse a code_verifier for a second sign-in?
No. Draw a fresh one for every authorization request: once a challenge has travelled in a URL it may sit in history, logs or a proxy.
Is plain ever the right choice?
Only when the client genuinely cannot compute a SHA-256, which is almost never. With plain the verifier itself rides in the authorization request; RFC 7636 §4.2 asks for S256 and §7.2 forbids falling back to plain after trying it.
Why does my challenge end with = when this page's does not?
Something encoded the digest with padding switched on. RFC 7636 §4.2 specifies base64url without padding, so strip the trailing `=`.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Run the open pane: draw a new pair, or check the pasted one.
- ⌘/Ctrl + S
- Download the generated pair as a file rather than the page.
- ⌘/Ctrl + C
- With focus in the tool and nothing highlighted, takes the generated pair, or the derived challenge on the Verify tab; highlight first and only that is taken.
- ⌘/Ctrl + Shift + C
- Copy the same values even while text is selected.
- ? or ⌘/Ctrl + /
- Open the in-tool shortcut help.