API key generator

Random API keys for a service you operate, generated in your browser and never transmitted anywhere.

G
More options
up to 10,000

Constraints

Read this first if you came here for an LLM key

This page generates a random string that you can issue as an API key for a service you run. It does not supply, unlock, retrieve or reveal an API key for OpenAI, Anthropic, Google Gemini or any other provider. Those keys are issued by the provider against a registered account, and no generator can produce a working one.

The reason is worth understanding, because it also explains what an API key is. A key has no meaning built into it. It is a random value that the issuing service recorded when it created it, and it works only because that service can look it up. A string generated here is not in anyone else's database, so it authenticates nothing except on a system where you have registered it yourself.

Any site offering working keys for a commercial API is either distributing stolen credentials or selling nothing at all. Using a key issued to someone else is account theft, and the provider revokes the key as soon as the usage pattern is noticed.

How long the key needs to be

The default here is 32 alphanumeric characters, which is 190.53 bits of entropy. That is far past what an API key needs, and the excess costs nothing: the string is machine-generated and machine-read, so length is only a storage question.

The floor is more interesting than the ceiling. NIST SP 800-63B-4 treats 64 bits as the point below which a secret has to be rate-limited to stay safe, and 112 bits as the mandated minimum security strength for a random bit generator. Above 128 bits, extra length buys nothing against brute force, because 128 bits is already unreachable. A key of 22 base62 characters is past that line.

What actually breaks API keys is never the key length. It is a key committed to a repository, pasted into a support ticket, printed in a stack trace, or logged by a proxy. The rest of this page is about those failures, because they are the ones that happen.

Entropy by length and alphabet
ValueAlphabetEntropyVerdict
16 charactersbase6295.27 bitsStrong. Fine behind rate limiting.
22 charactersbase62130.99 bitsPast the point where more bits help.
32 charactersbase62190.53 bitsThe default here. The surplus is free.
32 charactershex128.00 bits exactlyA 16-byte key written in hex.
43 charactersbase64url from 32 bytes256 bitsA 32-byte key, encoded.

Prefix the key so it can be recognised and revoked

A bare random string tells you nothing when you find it somewhere it should not be. A prefixed one tells you which system issued it and which environment it belongs to. Stripe writes sk_live_ and pk_test_, GitHub writes ghp_, AWS access key IDs start with AKIA. The convention exists because it makes a leaked credential identifiable at a glance and greppable in a log archive.

It also makes automated detection possible. Secret scanners work by pattern, and a distinctive fixed prefix is the pattern. A 32-character alphanumeric string with no prefix is indistinguishable from a build hash or a filename, so nothing will ever flag it.

Put the environment in the prefix rather than in a separate field. A production key pasted into a staging config is then obvious on sight instead of being discovered when it starts serving real traffic.

The prefix is not secret and adds no entropy, so it does not shorten the random part. Generate the full random string and concatenate. Of the fifteen generators audited during research, randomkeygen is the one whose own security guidance recommends sk_ and pk_ prefixes, and it provides no field for entering one.

Store a hash of the key, not the key

Your database should hold the SHA-256 of each key, not the key. When the row is created you show the value once and never again. If the table is later exposed, the attacker gets digests that authenticate nothing.

This is the one place where a fast hash is the correct choice, and it is worth being precise about why. Slow password hashes exist because human-chosen passwords have too little entropy to survive a fast guessing attack. A 190-bit uniformly random key has no such problem: there is no dictionary of API keys, and no wordlist will contain yours. SHA-256 is right here and Argon2id would only make every request slower.

Hashing does create a lookup problem, since you cannot search for a value you cannot reverse. The usual answer is the same split that produces the prefix convention: store a non-secret identifier and a short display fragment in the clear, index on those, and compare the hash once the row is found. Keep a last-used timestamp on each key as well, because it is the only way to tell a live credential from a forgotten one when you come to clean up.

Keep the key out of URLs, and plan for rotation

A key in a query string is written down repeatedly and by parties you do not control. It lands in your web server access log, in the reverse proxy log, in the CDN log, in the customer's outbound proxy log, in browser history, and in the Referer header sent to whatever the page links to next. None of those places is designed to hold secrets and none of them is covered by your key rotation plan.

This is not hypothetical. random.org generates strings server side by navigating to a URL of the form /strings/?num=..&len=.., so the value the visitor asked for travels as a query parameter and is written to an access log by construction. That is a design consequence of generating remotely, and it is the concrete reason this tool does its work in the browser.

Send keys in an Authorization header. For rotation, allow at least two live keys per account so that replacing one is not an outage: issue the new key, deploy it, confirm from the last-used timestamps that the old key has gone quiet, then revoke it. A rotation scheme that requires a simultaneous cutover will not be used, and a rotation scheme that is not used is not a rotation scheme.

Doing this in your own code

JavaScript (browser and Node 19+)
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

function apiKey(length = 32) {
  const n = ALPHABET.length;                  // 62
  const limit = Math.floor(2 ** 32 / n) * n;  // discard at or above this
  const buf = new Uint32Array(length);
  crypto.getRandomValues(buf);
  let out = "";
  for (let i = 0; i < length; i++) {
    let v = buf[i];
    while (v >= limit) v = crypto.getRandomValues(new Uint32Array(1))[0];
    out += ALPHABET[v % n];
  }
  return out;
}

"sk_live_" + apiKey();

Mapping raw bytes onto 62 symbols with a bare % is where modulo bias gets introduced. The discard loop is what removes it, and at 62 symbols it fires roughly once in a billion draws.

Python 3.6+
import secrets, string

# token_urlsafe(n) takes a BYTE count, not a character count.
secrets.token_urlsafe(32)   # 32 bytes -> 256 bits -> 43 characters
secrets.token_hex(16)       # 16 bytes -> 128 bits -> 32 characters

# Exactly 32 base62 characters. choice() is backed by randbelow(),
# which rejects out-of-range draws instead of taking a modulo.
ALPHABET = string.ascii_letters + string.digits
"".join(secrets.choice(ALPHABET) for _ in range(32))

The random module is a Mersenne Twister and its internal state is recoverable from its output. It must never appear in this code path.

Go
import (
    "crypto/rand"
    "encoding/base64"
    "math/big"
)

// 32 random bytes as base64url with no padding: 43 characters, 256 bits.
b := make([]byte, 32)
rand.Read(b)
token := base64.RawURLEncoding.EncodeToString(b)

// Exactly 32 base62 characters.
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
out := make([]byte, 32)
for i := range out {
    n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(alphabet))))
    out[i] = alphabet[n.Int64()]
}

crypto/rand, never math/rand. rand.Int is already unbiased; it rejects internally so you do not have to.

Rust
// rand = "0.9"
use rand::distr::{Alphanumeric, SampleString};

// 32 characters of A-Za-z0-9 from the thread CSPRNG (ChaCha12,
// seeded from the operating system and reseeded periodically).
let key = Alphanumeric.sample_string(&mut rand::rng(), 32);

// Raw bytes straight from the OS, for when the value is a key rather
// than a printable token. In rand 0.9 OsRng is fallible, so it offers
// try_fill_bytes rather than fill_bytes.
use rand::rngs::OsRng;
use rand::TryRngCore;
let mut k = [0u8; 32];
OsRng.try_fill_bytes(&mut k).expect("OS RNG unavailable");
PHP 7+
// 32 bytes as hex: 64 characters, 256 bits.
bin2hex(random_bytes(32));

// base64url, padding stripped: 43 characters.
rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');

// Exactly 32 base62 characters. random_int() is unbiased.
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$key = '';
for ($i = 0; $i < 32; $i++) {
    $key .= $alphabet[random_int(0, strlen($alphabet) - 1)];
}

random_bytes() and random_int() throw when the platform cannot supply entropy. mt_rand() and rand() return something wrong instead, which is why they are unusable here.

Java 8+
import java.security.SecureRandom;
import java.util.Base64;

SecureRandom rng = new SecureRandom();

byte[] raw = new byte[32];
rng.nextBytes(raw);
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(raw);

// Exactly 32 base62 characters. nextInt(bound) is unbiased.
String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder(32);
for (int i = 0; i < 32; i++) {
    sb.append(ALPHABET.charAt(rng.nextInt(ALPHABET.length())));
}
C# / .NET
using System.Security.Cryptography;

// 32 bytes as base64url with no padding.
byte[] raw = RandomNumberGenerator.GetBytes(32);
string token = Convert.ToBase64String(raw)
    .TrimEnd('=').Replace('+', '-').Replace('/', '_');

// Exactly 32 base62 characters. GetString rejects internally. .NET 8 and later.
const string Alphabet =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
string key = RandomNumberGenerator.GetString(Alphabet, 32);
Shell
# 32 bytes as hex: 64 characters.
openssl rand -hex 32

# base64url, padding stripped: 43 characters.
openssl rand -base64 32 | tr '+/' '-_' | tr -d '='

# 32 base62 characters, straight from the kernel CSPRNG.
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32; echo

The tr pipeline discards every byte outside the alphabet rather than folding it back in, which is rejection sampling under another name, so the output is uniform.

Questions

Can this generate a working OpenAI, Claude or Gemini API key?

No. Those keys are issued by the provider against a paid account and exist in the provider's own database. A random string is not in that database, so it will be rejected on the first request. No tool anywhere can generate one, and any site claiming otherwise is offering either stolen credentials or nothing.

Should an API key contain symbols?

Usually not. Keys travel through HTTP headers, URLs, .env files, YAML, JSON and shell commands, and every symbol is a chance for one of those to mangle it. Alphanumeric or base64url covers every case. The specific trap is the plus sign: in form-encoded content a + decodes to a space, so a token containing one silently becomes a different string when it is placed unencoded in a query.

Is a UUID good enough as an API key?

A version 4 UUID has 122 bits from a secure source, so the entropy is fine. The problems are conventional rather than mathematical: it carries no prefix, so a leaked one cannot be traced to a system or an environment, and people treat UUIDs as public identifiers, which means they end up in URLs and logs. A version 1 or version 7 UUID is worse still, because most of the value is a predictable timestamp.

Should I encrypt the keys in my database?

Hash them instead. Encryption means you can recover the plaintext, which means an attacker who reaches the key material can too, and you gain nothing from being able to display a key after issue. Store SHA-256, show the value once at creation, and let the customer generate a replacement if they lose it.

How often should keys be rotated?

Rotation on a fixed calendar is much less useful than rotation on an event: someone leaves the team, a laptop is lost, a key appears in a public repository, a vendor is dropped. What matters more is that rotation is cheap enough to do immediately when one of those happens, which means supporting two live keys and tracking last use.

Is a key generated in a browser safe to use in production?

Yes. The value comes from crypto.getRandomValues(), which is the same operating system CSPRNG that a server-side library would reach. It is never transmitted, and you can confirm that by watching the network panel while you generate. The one thing to check is that you trust the machine you are sitting at, which is equally true of a terminal running openssl rand.

Related generators