API Key Generator

Generate API keys with 128 to 512 random bits (122 for UUID v4), in the format your system expects. Each key comes with its SHA-256 hash in the second column: the key goes to your user once, and the hash is what belongs in your database.

API Keys (give these out)
SHA-256 (store these)

1 API Key Generator example

One key per format

hex (32 bytes)     4f1c0e9a7d2b...e83a (64 hex chars, 256 bits)
base64url          sk_live_uLha-a4ksExdU0PxS_3AReyk2hjtKo7Z5eTpuSYhwCg
base62             sk_live_3B9uYL9oSV7OgjIKZEmRZIZEUnqYi0jDECYWwgKfq3I
UUID v4            1e27e956-52fc-4d8e-b165-082925499078
prefix + checksum  myapp_Gl22VAKLFtpJgrrRE1WMxrmaUNaHhuDg9A0Vf3bt5Ej2YmHfL

2 The prefix + checksum format

myapp_ Gl22VAKLFtpJgrrRE1WMxrmaUNaHhuDg9A0Vf3bt5Ej 2YmHfL
^      ^                                           ^
|      random body, 43 base62 chars (256 bits)     CRC32(body) in base62, 6 chars
prefix: who issued it, which environment

This follows the layout GitHub moved to in 2021 for its tokens (ghp_, gho_ and friends): a short prefix that names the token type, a random base62 body, and a CRC32 checksum of the body in the last six characters. The prefix makes leaked keys easy to grep for and lets secret scanners match them with very few false positives. The checksum lets your API reject a mistyped or truncated key without a database lookup, and lets a scanner confirm a match offline.

Here the checksum is CRC32 (the zlib polynomial) of the body only, not the prefix, written as base62 with the digits-first alphabet (0-9A-Za-z) and left-padded to 6 characters. The Check box at the bottom recomputes it. CRC32 detects typos but adds no security: all of the key's strength comes from the random body.

3 Store the hash, not the key

Show the key to the user once, save sha256(key), and on each request hash the incoming key and look up the hash. A database leak then exposes nothing usable. SHA-256 is enough for API keys. Unlike passwords, they do not need bcrypt or Argon2: with 128 or more random bits there is nothing to brute force, and a fast hash keeps the lookup cheap on every request. Store the prefix and the last four characters in plain text if you want to show users which key is which.

Keys are generated with crypto.getRandomValues() and hashed with crypto.subtle in this tab. Nothing is sent anywhere. For production keys, we recommend generating them on the server that stores the hash, so the key never passes through a clipboard.

How to use the API Key Generator tool

  1. Choose a Format: Hex, Base64url, Base62, UUID v4, or Prefix + base62 + checksum.
  2. Choose the Randomness (16 to 64 bytes) and type a Prefix such as sk_live_ if you want one. UUID ignores both.
  3. Set Count. Keys appear in the left panel with their entropy; the SHA-256 of each key appears on the same line on the right.
  4. Copy Keys for the values you hand out and Copy hashes for what goes in the database. Generate makes a new batch.
  5. Paste any checksum-format key into Check a checksum key at the bottom to confirm the last six characters match.

How to generate and store an API key in Node.js, Python and PHP

Node.js

const crypto = require("node:crypto");
const zlib = require("node:zlib");

const B62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

function base62(len) {
  let out = "";
  while (out.length < len) {
    for (const b of crypto.randomBytes(len * 2)) {
      if (b < 248 && out.length < len) out += B62[b % 62]; // 248 = 4 x 62, no bias
    }
  }
  return out;
}

function toBase62(n, width) {
  let s = "";
  do { s = B62[n % 62] + s; n = Math.floor(n / 62); } while (n > 0);
  return s.padStart(width, "0");
}

function newKey(prefix) {
  const body = base62(43);                       // ~256 bits
  const check = toBase62(zlib.crc32(body), 6);   // zlib.crc32 needs Node 20.15+
  return prefix + body + check;
}

const key = newKey("myapp_");
const hash = crypto.createHash("sha256").update(key).digest("hex");
console.log(key);   // give to the user once
console.log(hash);  // store this

// Simple formats
console.log(crypto.randomBytes(32).toString("hex"));
console.log(crypto.randomBytes(32).toString("base64url"));
console.log(crypto.randomUUID());

On lookup, hash the incoming key and query by hash. Compare with crypto.timingSafeEqual if you compare in application code rather than in the database.

Python

import hashlib
import secrets
import zlib

B62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

def to_base62(n, width):
    s = ""
    while True:
        n, r = divmod(n, 62)
        s = B62[r] + s
        if n == 0:
            return s.rjust(width, "0")

def new_key(prefix):
    body = "".join(secrets.choice(B62) for _ in range(43))
    return prefix + body + to_base62(zlib.crc32(body.encode()), 6)

def checksum_ok(key):
    rest = key.rsplit("_", 1)[-1]
    return to_base62(zlib.crc32(rest[:-6].encode()), 6) == rest[-6:]

key = new_key("myapp_")
print(key, checksum_ok(key))
print(hashlib.sha256(key.encode()).hexdigest())

print(secrets.token_hex(32))      # 64 hex chars
print(secrets.token_urlsafe(32))  # base64url, 43 chars

PHP

<?php
const B62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

function toBase62(int $n, int $width): string
{
    $s = '';
    do {
        $s = B62[$n % 62] . $s;
        $n = intdiv($n, 62);
    } while ($n > 0);
    return str_pad($s, $width, '0', STR_PAD_LEFT);
}

function newKey(string $prefix): string
{
    $body = '';
    for ($i = 0; $i < 43; $i++) {
        $body .= B62[random_int(0, 61)];
    }
    return $prefix . $body . toBase62(crc32($body), 6);
}

$key = newKey('myapp_');
echo $key, PHP_EOL;
echo hash('sha256', $key), PHP_EOL;     // store this

echo bin2hex(random_bytes(32)), PHP_EOL; // plain hex key

crc32() returns an unsigned value on 64-bit PHP, which is what toBase62() expects. Use hash_equals() when comparing hashes yourself.

Common errors and how to fix them

Keys leak into logs and error trackers
Access logs, APM tools and exception reporters often record headers and query strings. Send keys in a header rather than the URL, and add a scrubbing rule that matches your prefix.
Base64 keys break in URLs or config files
Standard Base64 contains + / and =. Use Base64url (- and _ and no padding) or base62, which has no punctuation at all.
Checksum mismatch on a key that was never touched
Check that both sides use the same base62 alphabet order (digits first here), the same CRC32 variant (zlib, IEEE polynomial) and compute it over the body only. Getting any of those different changes every checksum.
Storing the hash means I cannot show the key again
That is intended. Show it once at creation, store the hash plus the prefix and last four characters for display, and let users rotate a lost key rather than recover it.

Good to know

Entropy is what makes a key unguessable, and the format only changes how it is written. 32 random bytes is 256 bits whether you print it as 64 hex characters, 43 base64url characters or 43 base62 characters. The checksum adds 32 bits of length but zero bits of security, since anyone can compute a CRC.

Specifications and references

This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.

Frequently Asked Questions

How long should an API key be?

At least 128 bits of randomness, and 256 is a comfortable default. That is 32 hex characters or 22 base62 characters for 128 bits. The entropy of the current format is shown above the list.

Should I store API keys hashed?

Yes. Store the SHA-256 of the key and compare hashes on each request, so a database leak does not hand out working keys. A fast hash is fine here because a random 256-bit key cannot be brute forced, unlike a password.

What is the prefix + checksum format?

A short prefix naming the issuer, a random base62 body, and a 6-character base62 CRC32 of the body at the end. It follows the layout GitHub adopted for its tokens: the prefix makes leaks easy to scan for, and the checksum catches typos without a database lookup.

Is a UUID good enough as an API key?

A UUID v4 from a secure generator has 122 random bits, which is enough to be unguessable. It is not self-describing, though, and many UUID libraries in the wild are not guaranteed to use a CSPRNG, so a prefixed random key is the better default.

Are the keys generated on a server?

No. They come from crypto.getRandomValues() in your browser and the hashes from crypto.subtle; the page sends nothing. For production, generating keys on the server that stores the hash keeps them out of the clipboard entirely.

Related

To sign requests with a shared secret instead of sending it, use the HMAC generator. The SHA generator hashes any text, and for human passwords use the password generator and store them with the bcrypt generator.

More Security & Hashing Tools

Browse all security & hashing tools

Copied to clipboard!