By AndyPublished

JWT Secret Key Generator: Create a Strong HS256, HS384 or HS512 Secret

A JWT secret for HS256 should be at least 32 random bytes (256 bits), produced by a cryptographically secure random number generator, never a word or passphrase a person chose. The generator below creates one in your browser. From a terminal, openssl rand -base64 32 does the same job.

The rest of this page explains where that 256-bit figure comes from (RFC 7518 §3.2), how to generate secrets with openssl, Node.js, Python and PowerShell, the Base64-versus-raw-bytes mistake that breaks signature verification, and why access tokens and refresh tokens should never share a secret.

JWT Secret Generator

Pick the size that matches your algorithm: 256 bits for HS256, 384 for HS384, 512 for HS512. Larger is allowed; smaller is not. The output is the same random bytes shown in two encodings, so choose the one your configuration expects and read the section on encodings below before pasting it anywhere.

Choose a size to generate a secret.

Secrets are generated in your browser with crypto.getRandomValues and never transmitted or stored.

Is an Online JWT Secret Key Generator Safe?

It depends entirely on where the randomness comes from and whether the value leaves your machine. A generator that calls crypto.getRandomValues in the page, as this one does, draws from the same operating-system CSPRNG that openssl rand uses, and the result exists only in your browser tab. A generator that fetches the secret from a server, or sends it anywhere for "analytics", gives a third party a copy of your signing key. You can check by opening the browser's network panel before clicking Generate: no request should appear. For production keys, generating on the server or inside your secrets manager is still the cleanest option, because the secret never passes through a clipboard.

What Is a JWT Secret Key?

HS256, HS384 and HS512 are HMAC algorithms defined in RFC 7518 §3.2. The issuer computes an HMAC over the encoded header and payload using a secret key, and the verifier recomputes it with the same key. If the two values match, the token has not been altered and was produced by someone holding the key. Because both sides need the identical secret, HMAC is a symmetric scheme: any service that can verify a token can also mint one.

That makes the secret the entire security of the token. The algorithm itself is sound; what fails in practice is the key. A guessable secret lets an attacker take any captured token and brute-force the key offline, with no rate limit and no log entry on your side. Once they have it, they can sign tokens with any sub, role or expiry they like. The header, payload and signature guide shows exactly which bytes the HMAC covers.

JWT Secret Size: How Long Should an HS256 Secret Be?

RFC 7518 §3.2 is explicit: "A key of the same size as the hash output (for instance, 256 bits for 'HS256') or larger MUST be used with this algorithm." The minimum therefore tracks the hash, not the token or the payload:

AlgorithmMinimum keyAs base64urlAs hex
HS25632 bytes (256 bits)43 characters64 characters
HS38448 bytes (384 bits)64 characters96 characters
HS51264 bytes (512 bits)86 characters128 characters

The requirement is about entropy, not string length. A 32-character password typed by a person has far less than 256 bits of entropy, so "32 characters" is not the same as "32 bytes". The RFC 8725 best current practice makes the same point: human-memorable passwords must not be used directly as HMAC keys.

Libraries differ in how strictly they enforce this. Recent PyJWT releases emit an InsecureKeyLengthWarning for a key shorter than the hash output, and the Java JJWT library refuses short HMAC keys with a WeakKeyException. In Node.js, both jose (6.x) and jsonwebtoken (9.x) will happily sign with a five-byte secret. Do not rely on the library to stop you.

How to Generate a JWT Secret Key From the Command Line

Every command below reads from the operating system's CSPRNG and produces 32 random bytes, which is enough for HS256. Change 32 to 48 or 64 for HS384 or HS512.

OpenSSL

# 32 random bytes, Base64 encoded (44 characters, ends with "=")
openssl rand -base64 32

# 32 random bytes, hex encoded (64 characters)
openssl rand -hex 32

Node.js

node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

Python

python3 -c "import secrets; print(secrets.token_urlsafe(32))"

PowerShell 7 and plain Unix tools

# PowerShell 7 (.NET 6+)
[Convert]::ToBase64String([Security.Cryptography.RandomNumberGenerator]::GetBytes(32))

# Any Unix-like system
head -c 32 /dev/urandom | base64

randomBytes(32).toString('base64url') and secrets.token_urlsafe(32) both print 43 URL-safe characters with no padding, which is convenient in .env files and YAML because there is no +, / or = to quote. Avoid Math.random(), uuidgen and Python's random module: they are either not cryptographically secure or, in the case of a version 4 UUID, carry only 122 random bits.

The Base64 vs Raw Bytes Pitfall

The output of openssl rand -base64 32 is a 44-character string that represents 32 bytes. A library can use that secret in two different ways:

  • ·As text: the 44 ASCII characters themselves become the HMAC key (44 bytes).
  • ·As decoded bytes: the string is Base64-decoded first and the 32 underlying bytes become the key.

Both are fine on their own. The failure happens when the issuer does one and the verifier does the other: the keys differ, and every token fails with "invalid signature". Here is the effect in Node.js with jose:

import { SignJWT, jwtVerify } from 'jose';

const secret = 'fp6K+eRso7YXF5YjbJ/c5QxSFM+LL7btginHiPgZzoU=';
const asText = new TextEncoder().encode(secret);   // 44 bytes
const asBytes = Buffer.from(secret, 'base64');      // 32 bytes

const token = await new SignJWT({ sub: '1' })
  .setProtectedHeader({ alg: 'HS256' })
  .sign(asBytes);

await jwtVerify(token, asBytes); // ok
await jwtVerify(token, asText);  // throws ERR_JWS_SIGNATURE_VERIFICATION_FAILED

Tools make the choice visible in different ways. Postman's JWT Bearer auth type has a "Secret Base64 encoded" checkbox, and the Rust jwt-cli accepts a b64: prefix on --secret. The Verify tab on the jwtdecode.app decoder treats the HMAC secret as UTF-8 text and runs entirely in the browser, so if it rejects a token that your server accepts, the server is probably decoding the secret first. Pick one convention, write it next to the variable in your configuration, and apply it on every service. The invalid signature section lists the other causes worth ruling out.

What Makes the Best JWT Secret Key?

  • ·Random, from a CSPRNG: generated by one of the commands above or the tool on this page, never typed.
  • ·At least as long as the hash: 32 bytes for HS256, 48 for HS384, 64 for HS512. Going longer costs nothing measurable.
  • ·Unique per purpose and environment: development, staging and production each get their own; so do access and refresh tokens.
  • ·Never committed: not in source control, container images, front-end bundles or example configuration files. A secret shipped to a browser is public.
  • ·Paired with a pinned algorithm: the verifier should accept only the HMAC algorithm you chose, never whatever the token header says. The none algorithm guide explains why.

How to Store and Rotate a JWT Secret

Load the secret at start-up from a secrets manager (AWS Secrets Manager, Google Secret Manager, Azure Key Vault, HashiCorp Vault, Kubernetes Secrets with encryption at rest) or, at minimum, an environment variable injected by the deployment system. Keep it out of logs and error reports; a stack trace that dumps configuration is a common leak.

Rotation is easiest when every token carries a kid header naming the key that signed it. The sequence is:

  • ·Add the new secret to every verifier, keyed by its new kid, alongside the old one.
  • ·Switch the issuer to sign with the new secret.
  • ·Wait for the longest-lived token signed with the old secret to expire, then remove the old secret.

If the secret has leaked, skip the waiting period: remove it immediately and accept that every user signed in with an old token must authenticate again. The kid header guide covers key lookup in more detail.

JWT Refresh Secret: Use a Separate Key

If your system issues its own refresh tokens as JWTs, generate a second secret for them and give it its own name, for example JWT_ACCESS_SECRET and JWT_REFRESH_SECRET. Sharing one secret means a refresh token is a validly signed token as far as your API is concerned; if the API only checks the signature and expiry, a long-lived refresh token can be replayed as an access token. Separate keys, plus a typ or token_use claim checked on both sides, close that gap. A separate secret also means the refresh endpoint's key can live only in the authentication service, while the access-token key is distributed to APIs.

Many teams skip JWTs for refresh tokens entirely and use an opaque random value stored server-side, which makes revocation a simple database delete. The refresh token pattern guide compares both.

When a Shared Secret Is the Wrong Tool

HMAC works well when the same service, or a small set of services you control, both issues and verifies tokens. Once tokens are verified by many services, by third parties, or by anything you do not fully trust, every verifier holding the secret is also able to forge tokens. That is the point to move to an asymmetric algorithm such as RS256 or ES256, where verifiers hold only a public key. See HS256 vs RS256 for the trade-offs and generating JWT signing keys with OpenSSL for the commands.

⚠
Do not paste a production secret into any website, including this one's generator or verifier, unless you understand where it goes. The generator above never sends anything anywhere, but the safest production secret is one generated on the machine or secrets manager that will use it.

Summary

  • ·RFC 7518 requires an HMAC key at least as large as the hash output: 256 bits for HS256, 384 for HS384, 512 for HS512.
  • ·Generate secrets with a CSPRNG: openssl rand -base64 32, crypto.randomBytes(32) or secrets.token_urlsafe(32).
  • ·Decide whether the secret is used as text or as decoded bytes, and make every service agree.
  • ·Store secrets in a secrets manager, rotate them with kid, and keep access and refresh secrets separate.
  • ·When many services verify tokens, switch to an asymmetric key pair instead of sharing the secret.
Ready to decode a token?
Use the free JWT decoder — paste any token for instant results, entirely in your browser.
Open JWT Decoder