Random 10 character string generator

Ten characters from a 62-symbol alphabet: 59.5 bits, generated locally with rejection sampling and no network call.

G
More options
up to 10,000

Constraints

59.5 bits, and the line it sits just under

Ten alphanumeric characters give 10 x log2(62) = 59.542 bits over a keyspace of 62^10, which is about 8.39 x 10^17 strings. Averaged over that space, an attacker with one GPU running unsalted MD5 at 220.6 GH/s needs about 22 days. Behind a slow hash it is not a realistic attack at all.

The detail worth carrying away is where 59.5 lands. NIST SP 800-63B-4 requires a verifier to rate limit any authentication secret shorter than 64 bits, and ten characters is 4.5 bits short of that. Eleven characters gives 65.50 bits and clears it. If a secret is going anywhere near a login or a token check, one extra character moves it across a standards boundary at no cost to anything.

That is the whole argument for 11 over 10, and it only applies to secrets. For a reference code that nobody authenticates with, the threshold is irrelevant and ten is a perfectly reasonable place to stop.

Entropy at 62 characters per symbol, and the NIST bands
LengthEntropyBand
847.63 bitsBelow the 64-bit rate-limiting threshold
1059.54 bitsBelow the 64-bit rate-limiting threshold
1165.50 bitsClears 64 bits
1271.45 bitsClears 64 bits
1695.27 bitsClears the 15-character password SHALL

Why reference codes land on ten

Order numbers, shipment tracking references, support ticket IDs, licence keys, short-lived download links. Ten characters is long enough that duplicates stop being an operational concern at any volume a business actually reaches, and short enough to read out in five groups of two without anyone losing their place. The 1% duplicate bound sits at about 130 million codes and the 50% point at about 1.08 billion.

The usual reason to pick a random reference over a sequential one is not secrecy, it is disclosure. Sequential order numbers tell any customer how many orders you took between two purchases. A random 10-character reference tells them nothing. That is a legitimate use even though 59.5 bits would be a poor password.

If you format the code for reading, keep the formatting out of the stored value. A hyphen after five characters helps a human and belongs in the presentation layer, not in the column and not in the comparison.

When ten characters is the wrong choice

When the value authenticates. A ten-character API key, session token or password reset link is under the NIST threshold and should be 16 characters or more. The 16-character page has the numbers.

When the value is a primary key at scale. Ten characters is not a good identifier for a table you insert into constantly: the duplicate bound is reachable, and a random key scatters inserts across the index. A UUIDv7 or a ULID gives you time ordering and a collision probability that never becomes interesting.

When a person will type it under time pressure. At ten characters a case-sensitive mixed alphabet produces transcription errors at a rate that is worth designing around. Uppercase and digits only, with look-alikes excluded, is the shape most physical voucher codes settle on.

Doing this in your own code

JavaScript
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";  // 62 characters

function randomString(length = 10) {
  // Largest multiple of 62 that fits in a uint32. Anything at or above it
  // is discarded, which is what keeps the mapping uniform.
  const limit = Math.floor(2 ** 32 / ALPHABET.length) * ALPHABET.length;
  const buf = new Uint32Array(1);
  let out = "";
  for (let i = 0; i < length; i++) {
    do { crypto.getRandomValues(buf); } while (buf[0] >= limit);
    out += ALPHABET[buf[0] % ALPHABET.length];
  }
  return out;
}

randomString();  // 10 characters

Without the rejection step, 2^32 does not divide evenly by 62 and the first four characters of the alphabet come up slightly more often.

Python 3.6+
import secrets
import string

ALPHABET = string.ascii_letters + string.digits  # 62 characters

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

secrets.choice goes through randbelow, which rejects out-of-range values internally. random.choice is unbiased as well, but it runs on the Mersenne Twister and must not be used here.

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

const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

func RandomString(n int) (string, error) {
	b := make([]byte, n)
	max := big.NewInt(int64(len(alphabet)))
	for i := range b {
		v, err := rand.Int(rand.Reader, max)
		if err != nil {
			return "", err
		}
		b[i] = alphabet[v.Int64()]
	}
	return string(b), nil
}

RandomString(10)

crypto/rand.Int already does rejection sampling, so the result is uniform. math/rand is not a substitute at any size.

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

random_string(10);

random_int is CSPRNG-backed and unbiased. rand(), mt_rand() and array_rand() are none of those things.

Java
import java.security.SecureRandom;

static final String ALPHABET =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
static final SecureRandom RNG = new SecureRandom();

StringBuilder sb = new StringBuilder(10);
for (int i = 0; i < 10; i++) {
    sb.append(ALPHABET.charAt(RNG.nextInt(ALPHABET.length())));
}
sb.toString();

nextInt(bound) rejects and redraws when the bound does not divide the range evenly, so no bias is introduced here.

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

const string Alphabet =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

var chars = new char[10];
for (int i = 0; i < chars.Length; i++)
{
    chars[i] = Alphabet[RandomNumberGenerator.GetInt32(Alphabet.Length)];
}
string value = new string(chars);

RandomNumberGenerator.GetInt32 is unbiased. System.Random is not cryptographic and is the wrong class for this job.

Shell
# Linux and macOS. LC_ALL=C stops tr rejecting bytes outside the locale.
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 10; echo

# openssl, if you want 10 hexadecimal characters instead
# (5 bytes = 10 hex characters = 40 bits, exactly)
openssl rand -hex 5

tr -dc discards bytes outside the set rather than folding them into it, so the one-liner is uniform. /dev/urandom is the kernel CSPRNG on both platforms.

Questions

How strong is a 10-character random string?

59.54 bits over the 62-character alphanumeric alphabet. That is roughly 22 days of average search on a single GPU against unsalted MD5, and effectively unreachable behind bcrypt or scrypt. It sits just under the 64-bit level below which NIST SP 800-63B-4 requires a verifier to rate limit attempts.

Why would I use 11 characters instead of 10?

Because 11 characters gives 65.50 bits and clears the 64-bit threshold in NIST SP 800-63B-4, while 10 gives 59.54 and does not. If the value is an authentication secret, that single character is the difference between needing rate limiting to be compliant and not.

How many 10-character codes can I issue?

The chance of at least one duplicate reaches 1% at about 130 million codes and 50% at about 1.08 billion. For almost any business use that is far beyond the volume you will ever issue, but still enforce uniqueness in the database rather than assuming it.

Can I use a 10-character string as a password?

It is stronger than most human-chosen passwords, but it does not meet NIST SP 800-63B-4, which sets a 15-character SHALL for single-factor use, or PCI DSS v4.0.1, which requires 12. Generate 16 characters instead; the cost of the extra length is nothing.

Does the generator ever repeat a string?

Each draw is independent, so a repeat is possible and its probability follows the birthday bound above. Turning on the unique-output option removes duplicates within a single batch; it cannot know about values you generated yesterday, which is what the database constraint is for.

Related generators