Random alphanumeric string generator

Letters and digits only, drawn uniformly from 62 characters. Sixteen characters carries 95.27 bits.

G
More options
up to 10,000

Constraints

Why 62 characters is the default for anything that travels

An alphanumeric string is the only common alphabet that needs no escaping anywhere. RFC 3986 defines the unreserved URL character set as A-Z, a-z, 0-9 and the four punctuation marks hyphen, period, underscore and tilde. Alphanumerics are a strict subset, so the value goes into a path segment, a query parameter or a fragment unchanged, with no percent-encoding and no decoding step to get wrong.

The same holds everywhere else it is likely to end up. It is a legal filename on every filesystem in use. It survives a shell argument without quoting. It needs no escaping inside a SQL string literal or a JSON string. It cannot start with an equals, plus, minus or at sign, so it will not be read as a formula when a CSV export is opened in Excel or Google Sheets. It is a valid environment variable value with no quoting rules attached.

Base64url carries more bits per character but adds hyphen and underscore, and standard Base64 adds plus and slash. The plus is the one that bites: in application/x-www-form-urlencoded a plus decodes to a space, so a Base64 token dropped unencoded into a query string silently becomes a different string. Alphanumeric buys its way out of that entire class of bug for the cost of 0.046 bits per character against Base64.

The entropy, exactly

For characters drawn independently and uniformly, entropy is length multiplied by log2 of the alphabet size. log2(62) is 5.954196 bits, which is not a round number and not exact, but it is a real number rather than an approximation. Sixteen characters is 95.267 bits. Thirty-two is 190.534.

Both halves of that formula are separate claims. The entropy equals log2 of the size of the string space only if the distribution is uniform, and the string space is 62 to the power of the length only if the characters are independent. A generator that breaks either one reports a number higher than the truth. That is why this page publishes the sampler rather than the marketing.

Characters needed at 62 symbols, from the entropy target
Target entropyCharactersActual bitsWhat that tier is for
64 bits1165.50NIST floor for a secret that needs no rate limiting
80 bits1483.36Session tokens, most API keys
112 bits19113.13The NIST-mandated minimum RBG security strength
128 bits22130.99Matches AES-128. Beyond this, brute force is not the threat
256 bits43256.03Symmetric key material

Sixty-two is not a power of two, which is where generators go wrong

Mapping a random byte onto 62 characters with alphabet[x % 62] is the standard mistake. 256 is 4 times 62 plus 8, so residues 0 to 7 have five preimages and residues 8 to 61 have four. The first eight characters of the alphabet come up exactly 1.25 times as often as the rest. Over 20,054,016 draws that produces a chi-square of 132,618.6 on 61 degrees of freedom, against a critical value of 100.888.

Shannon entropy barely notices, falling 0.0045 bits per character. Min-entropy, which is what an attacker searching most-likely-first actually faces, falls 0.276 bits per character. Over sixteen characters that is 4.418 bits, so the real search space is about 21 times smaller than the tool claims it is.

The fix is rejection sampling against the largest multiple of the alphabet size. Drawing 32-bit words instead of bytes makes it free: 2 to the 32 is 69,273,666 times 62 plus 4, so exactly four values in 4,294,967,296 are discarded and redrawn. That is one rejection per 1.07 billion draws. Our own sampler, over 400,000 strings of length 16, returns a chi-square of 72.332 on 61 degrees of freedom and a pooled per-position chi-square of 969.58 on 976.

The figure sometimes quoted for modulo bias, 1.0323, is 256 divided by 248. It is the expected number of draws per accepted output under rejection sampling, which is a performance number. The bias ratio is 1.25.

Case-insensitive storage silently costs you a third of the alphabet

An alphanumeric string only carries 5.954 bits per character if the system that reads it back distinguishes upper from lower case. Several common ones do not. MySQL ships a case-insensitive collation by default, so a token stored in a varchar column matches regardless of case unless the column or the comparison is explicitly made binary. Windows file names are case-insensitive, macOS is case-insensitive by default on APFS, DNS labels are case-insensitive by specification, and so are HTTP header field names.

When case is folded, 62 characters become 36. The entropy per character drops from 5.954196 to 5.169925, a loss of 0.784 bits per character. A 16-character token goes from 95.27 bits to 82.72, which is 13.2% of its strength, and the loss is invisible because the string still looks the same.

If the value has to pass through anything case-insensitive, generate it from a case-insensitive alphabet in the first place so the entropy figure on screen is the entropy you actually have. Digits with uppercase letters, or one of the curated 32-character alphabets, is the honest choice there.

Doing this in your own code

JavaScript
// Uniform over the alphabet, with no modulo bias.
// The alphabet is spread into code points first, so an emoji or an
// accented character counts as one symbol rather than two UTF-16 units.
function randomString(length, alphabetStr) {
  const alphabet = [...new Set([...alphabetStr])];
  const n = alphabet.length;
  if (n < 1) throw new RangeError("empty alphabet");

  const limit = 4294967296 - (4294967296 % n);   // largest multiple of n <= 2^32
  const buf = new Uint32Array(1);
  let out = "";

  for (let i = 0; i < length; i++) {
    let x;
    do { crypto.getRandomValues(buf); x = buf[0]; } while (x >= limit);
    out += alphabet[x % n];
  }
  return out;
}

randomString(16, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");

The do/while is the whole point. Dropping it in favour of a bare x % n reintroduces modulo bias.

Python 3.6+
import secrets

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

"".join(secrets.choice(ALPHABET) for _ in range(16))

# secrets.choice is backed by secrets.randbelow, which rejects and redraws,
# so it is uniform. random.choice is not a secure source and must not be used.

Iterating a Python str yields code points, so non-ASCII alphabets work without extra handling.

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

func RandomString(length int, alphabet string) (string, error) {
    runes := []rune(alphabet)
    n := big.NewInt(int64(len(runes)))
    out := make([]rune, length)
    for i := range out {
        j, err := rand.Int(rand.Reader, n)   // rejection sampled internally
        if err != nil {
            return "", err
        }
        out[i] = runes[j.Int64()]
    }
    return string(out), nil
}

Convert to []rune, not []byte. Indexing a Go string yields bytes, which splits any multi-byte character.

Rust
// rand = "0.9"
use rand::Rng;

const ALPHABET: &[u8] =
    b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

fn random_string(length: usize) -> String {
    // rand::rng() is a thread-local ChaCha12 CSPRNG seeded from the OS.
    let mut rng = rand::rng();
    (0..length)
        .map(|_| ALPHABET[rng.random_range(0..ALPHABET.len())] as char)
        .collect()
}

random_range rejection-samples, so it is unbiased. On rand 0.8 the method is named gen_range and the constructor is rand::thread_rng().

PHP 7+
<?php
function random_string(int $length, string $alphabet): string {
    $n = strlen($alphabet);
    $out = '';
    for ($i = 0; $i < $length; $i++) {
        $out .= $alphabet[random_int(0, $n - 1)];
    }
    return $out;
}

echo random_string(16, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789');

random_int throws on entropy failure and is unbiased. rand and mt_rand are neither.

Java 8+
import java.security.SecureRandom;

static String randomString(int length, String alphabet) {
    SecureRandom rng = new SecureRandom();
    StringBuilder sb = new StringBuilder(length);
    for (int i = 0; i < length; i++) {
        sb.append(alphabet.charAt(rng.nextInt(alphabet.length())));
    }
    return sb.toString();
}

nextInt(bound) rejects and redraws, so it is uniform. Reuse one SecureRandom instance rather than constructing one per call.

C# / .NET
using System.Security.Cryptography;

const string Alphabet =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

// .NET 8 and later
string s = RandomNumberGenerator.GetString(Alphabet, 16);

// Earlier versions, same distribution
var sb = new System.Text.StringBuilder(16);
for (int i = 0; i < 16; i++)
    sb.Append(Alphabet[RandomNumberGenerator.GetInt32(Alphabet.Length)]);
Shell
# 16 alphanumeric characters from the kernel CSPRNG.
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16; echo

# tr -dc discards every byte outside the set rather than folding it in,
# which makes it a rejection sampler. Each of the 62 bytes has exactly one
# preimage, so the output is uniform.

# openssl, when you want bytes rather than a character set
openssl rand -hex 16
openssl rand -base64 24

Questions

How long should an alphanumeric string be?

It depends on what it protects. Eleven characters reaches 64 bits, which NIST SP 800-63B-4 treats as the floor for a secret that stands on its own without rate limiting. Fourteen reaches 80 bits and is a reasonable session token. Twenty-two reaches 128 bits and matches AES-128, past which brute force stops being the interesting attack. Sixteen, the default here, is 95.27 bits.

Is an alphanumeric string safe to put in a URL?

Yes, with no encoding. RFC 3986 lists A-Z, a-z and 0-9 as unreserved characters, so they are legal in every part of a URI without percent-encoding. This is the main practical reason to prefer 62 characters over Base64, which contains plus and slash, or Base64url, which contains hyphen and underscore.

Why not use Math.random() with a rounding step?

Two separate problems. Math.random() in V8 is xorshift128+, a non-cryptographic generator whose internal state can be recovered from a small number of outputs, after which every future value is predictable. Separately, the usual index calculation introduces bias. Of the fifteen generators we audited on page one of Google, five use Math.random() in their generator logic.

Does excluding look-alike characters weaken the string?

It costs bits per character but nothing else, and the cost is small. Going from 62 characters to the 49-character nolookalikes alphabet loses 0.3395 bits per character, which is 5.70%. One extra character every 16.5 characters recovers it exactly: a 16-character alphanumeric string is 95.267 bits and a 17-character nolookalikes string is 95.450.

Are these strings unique?

Each one is drawn independently, so duplicates are possible in principle and astronomically unlikely at this length. At 16 characters the space is 62 to the 16, about 4.77 times 10 to the 28. Duplicates only become a practical concern at four characters or fewer, which is the regime coupon codes and PINs live in, and the tool offers cross-batch deduplication with a feasibility check for exactly that case.

Related generators