A password has to be typed and remembered, so it trades strength for usability. A secret for a machine, such as a JWT signing key, session secret, webhook secret or API key, is never typed by anyone. It should be long, random, and generated by a computer.
The Password Generator is good for human passwords. For machine secrets, use the commands below.
One-liners
Terminal (macOS/Linux):
openssl rand -base64 32 # 32 bytes = 256 bits, base64 openssl rand -hex 32 # same entropy, 64 hex chars
Node.js:
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
Python:
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
All three use the operating system's cryptographically secure random source. Never use Math.random(), random.random(), timestamps, or UUID v1 for secrets. They're predictable.
How long?
| Use | Minimum |
|---|---|
| JWT HS256 signing key | 32 bytes (256 bits) |
| JWT HS512 signing key | 64 bytes |
| Session / cookie secret | 32 bytes |
| Webhook signing secret | 32 bytes |
| API key for customers | 32 bytes of randomness, plus a prefix |
| Encryption key (AES-256) | exactly 32 bytes, stored as raw bytes or base64 |
32 random bytes is far beyond brute-force range. Going longer doesn't hurt, but it doesn't fix a secret that leaked.
Hex vs base64: the encoding doesn't change the strength. 32 bytes is 64 characters in hex or about 43 in base64url. Use base64url when the value goes into URLs or headers, because it avoids + and /.
Designing API keys for your users
If you issue keys to customers, a few conventions save a lot of pain:
import { randomBytes, createHash } from "node:crypto"; export function createApiKey() { const secret = randomBytes(32).toString("base64url"); const key = `sk_live_${secret}`; const hash = createHash("sha256").update(key).digest("hex"); const prefix = key.slice(0, 12); // "sk_live_AbCd" return { key, hash, prefix }; // show `key` once; store hash + prefix }
- Prefix the key (
sk_live_,sk_test_). Humans can tell environments apart, and secret scanners such as GitHub's can spot leaked keys. - Store only a hash. If the database leaks, the keys don't. A fast hash like SHA-256 is fine here because the input is already high-entropy random data. Slow password hashes are for low-entropy human passwords.
- Show it once. After creation, display only the prefix.
- Let users have several keys so they can rotate without downtime.
- Compare in constant time (
crypto.timingSafeEqual) when you check a key against its hash.
Storing your own secrets
- Put them in environment variables or a secret manager, never in the repo.
- Add
.env*to.gitignorebefore the first commit. - Use different secrets for development, staging and production.
- If one leaks, rotate first, then clean up. See Remove a Leaked .env File From Git History.
Rotating a JWT secret without logging everyone out
Support two keys for a while: sign new tokens with the new key, and verify against both. Put a key id (kid) in the token header so you know which key to check. Once the longest-lived token has expired, remove the old key.