How to Generate Strong API Keys and Secrets (Not Passwords)

Suman SharmaSuman Sharma

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?

UseMinimum
JWT HS256 signing key32 bytes (256 bits)
JWT HS512 signing key64 bytes
Session / cookie secret32 bytes
Webhook signing secret32 bytes
API key for customers32 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 }
  1. Prefix the key (sk_live_, sk_test_). Humans can tell environments apart, and secret scanners such as GitHub's can spot leaked keys.
  2. 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.
  3. Show it once. After creation, display only the prefix.
  4. Let users have several keys so they can rotate without downtime.
  5. 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 .gitignore before 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.

Frequently asked questions

How long should a JWT secret be?

For HS256, use at least 256 bits (32 random bytes) from a cryptographically secure generator. That is 64 hex characters or 44 base64 characters. A human-made phrase is not enough.

What is the best command to generate a secret key?

openssl rand -base64 32 on macOS and Linux. In Node, crypto.randomBytes(32).toString('base64url'). In Python, secrets.token_urlsafe(32).

Should I store API keys in plain text in my database?

No. Store a hash of the key, show the full key to the user once, and keep a short prefix in plain text so users can recognise which key is which.

Contact

Get in Touch

Want to call me? book a call and I'll respond in the available time slot. I will ignore all soliciting.