JWT Signer
EncodingEverything runs locally in your browser — nothing is uploaded
This supplies the half that was missing from decode-only: fill in a payload, pick an algorithm, provide a key, and get a JWT you can use. ⚠️ The key goes in the text area, not the options, because a PEM is necessarily multi-line and a single-line input cannot hold one: ``` { "sub": "1234567890" } --- -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- ``` A line containing only `---` separates the payload from the key. With no separator, the whole input is the payload — and an HS* secret can go in the single-line option field, since a base64 secret is short. ⚠️ On pasting a production signing key into a web page: everything here runs locally, nothing is uploaded or stored — but pasting a production key into any web page, this one included, is still not advisable. Not because you should distrust this page, but because you cannot audit whether a web page does only what it claims, while you can audit your own script. It is convenient for test tokens; for production, use a command that runs on your own machine. That warning sits at the top of the result. ⚠️ The private key must be PKCS#8 (`-----BEGIN PRIVATE KEY-----`). `BEGIN RSA PRIVATE KEY` (PKCS#1) and `BEGIN EC PRIVATE KEY` (SEC1) are both recognised here and come with the `openssl` command to convert them — feeding their base64 straight to the browser crypto API does not report a format problem, it raises an error with no useful explanation.
Features
- 13 algorithms: HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512 and EdDSA
- ⚠️ The “do not paste a production key into a web page” warning is at the top of the result, not buried in the FAQ
- ⚠️ A token with no `exp` is called out as never expiring — forgetting it means issuing a credential that cannot be revoked by time
- Fills in `iat` and `exp` automatically (`iat` only when you left it out, so a time you meant to construct is not overwritten)
- Validity from 5 minutes to a year, or none at all if you write `exp` yourself
- ⚠️ Private keys must be PKCS#8; PKCS#1 and SEC1 are detected and come with the `openssl pkcs8 -topk8` conversion
- ⚠️ ES512 uses the P-521 curve (there is no “P-512”)
- ⚠️ ES* signatures are emitted as JWS fixed-width r‖s, not the DER encoding ECDSA usually uses
- ⚠️ PS* uses the JOSE-mandated salt length (the hash length), not the browser API default
- Header and payload results are both copyable as a whole
- Runs entirely in your browser; the key is never uploaded
How to use
- Write the payload JSON in the text area (for a private key, add a `---` line and paste the PEM below)
- Pick an algorithm; an HS* secret can go straight into the option field
- Choose a validity period; the result gives the JWT, header, payload and signature
FAQ
- Why warn me off signing production tokens here?
- Because this is a web page, and you cannot audit whether it does only what it claims — while you can audit your own script. This page genuinely runs locally and uploads nothing, but “trust my word for it” and “read the code yourself” are different things, especially when what you paste is a production signing key. For test tokens and poking at an API, this is convenient. For production, use a command or library that runs on your own machine.
- Why does the key go in the text area rather than an option?
- Because a PEM private key is necessarily multi-line (header, base64 body, footer), and the generic “text” option renders as a single-line input that physically cannot hold one. So the convention is a line containing only `---`: payload above, key below. An HS* secret is a single line of base64 and fits in the option field, so both routes work.
- Why was my RSA private key rejected?
- Almost always a format issue. The browser crypto API accepts PKCS#8 only (`-----BEGIN PRIVATE KEY-----`), while many tools export PKCS#1 (`-----BEGIN RSA PRIVATE KEY-----`), and EC keys have a third form, SEC1 (`-----BEGIN EC PRIVATE KEY-----`). All three are PEM and look nearly identical, but the internal structure differs. Handing PKCS#1 base64 straight to the API does not report a format error — it raises an error with no discernible cause. So they are detected here, with `openssl pkcs8 -topk8 -nocrypt -in old.pem -out new.pem` given as the fix.
- How do I verify the token I just signed?
- For HS*, compute the same HMAC with the same secret and compare. For RS, PS, ES and EdDSA, give the verifier the public key — which is the point of asymmetric algorithms: the verifier never needs a key that can sign. Note that ES* signatures are fixed-width r‖s in JWS rather than DER, and some libraries need that encoding stated explicitly, otherwise verification fails with no obvious cause.
- What happens with no exp?
- Nearly every verification library treats a token with no `exp` as valid forever. That is not a hole in the spec — `exp` is optional, and omitting it means no expiry. So forgetting it at signing time issues a credential that never lapses, and it looks exactly like a normal token. This tool says so in the result.
Related tools
JWT Decoder & Verifier
EncodingDecode a JWT and verify an HS256, HS384 or HS512 signature with your secret, reading exp and nbf as dates. All local to your browser.
Hash
EncodingCompute MD5, SHA-1, SHA-256, SHA-512 or MD4 digests of any text, hashed as UTF-8 bytes so they match openssl exactly. Runs in your browser.
HMAC
EncodingSign a message with a secret key using HMAC and get a result matching Node, Java and Python. The key never leaves your browser — nothing is uploaded.
Base64
EncodingConvert text to Base64 and back, with correct UTF-8 handling for accents, CJK and emoji. Runs entirely in your browser — nothing is uploaded.
URL Encode
EncodingEscape text into %XX form and back again, treating a whole URL and a single parameter value as different jobs. Runs entirely in your browser.
HTML Entities
EncodingTurn < > & and quotes into HTML entities and decode them back in a single pass, so &lt; becomes <, not an angle bracket. Runs in your browser.