Random 12 character string generator

Twelve characters from a 62-symbol alphabet: 71.5 bits, a defensible floor for a password a human still has to type.

G
More options
up to 10,000

Constraints

What 71.5 bits buys

Twelve alphanumeric characters give 12 x log2(62) = 71.450 bits over a keyspace of about 3.23 x 10^21. That clears the 64-bit level below which NIST SP 800-63B-4 requires a verifier to rate limit attempts, which is the first threshold a generated secret has to pass to be defensible on its own.

It is a reasonable floor for a password a person has to type, and that is the honest framing: twelve is where the trade between typeability and strength stops being embarrassing, not where it becomes comfortable. If nobody has to type it, there is no reason to stop here.

Twelve passes PCI and fails NIST

PCI DSS v4.0.1 requirement 8.3.6 has been in force since 31 March 2025 and asks for a minimum of 12 characters containing both numeric and alphabetic characters. NIST SP 800-63B-4 requires 15 characters for single-factor use, as a SHALL, and separately states that other composition requirements for passwords SHALL NOT be imposed. The two documents contradict each other on both counts.

You cannot satisfy the spirit of both, but you can satisfy the letter of both by generating longer than either minimum. A uniform 16-character alphanumeric string is 95.27 bits, exceeds PCI on length, exceeds NIST on length, and satisfies PCI class checking about 94% of the time on the first draw.

If your validator demands a class mix, generate with the class constraint switched on. This tool implements it by rejecting and regenerating the whole string until it qualifies, which keeps the output uniform over the set of valid strings. The common alternative, forcing one character of each class into the string and shuffling, is not uniform over the valid strings even when the shuffle is perfect, so the real entropy falls below the number a tool displays.

The hash decides, not the length

Averaged over the keyspace, 71.45 bits falls in about 232 years against unsalted MD5 on one GPU at 220.6 GH/s. On a hundred thousand of those GPUs it is about 20 hours. Behind bcrypt at cost 12, at roughly 2,381 hashes per second, the same string takes on the order of 10^10 years.

The ratio between those rates is 2^26.5. Put plainly, a secret stored behind unsalted MD5 costs an attacker about 26 bits less work than the same secret behind bcrypt at cost 12, so a 71.5-bit password in an MD5 database is worth roughly 45 bits of real protection. That is the argument for choosing 16 characters when you have no control over how the far end stores it.

None of this is affected by which characters appear in the string. Entropy comes from the size of the space and the uniformity of the draw. A random 12-character string with no symbols in it is exactly as strong as one that happens to contain three.

Twelve characters is the shortest length on this site we would defend for a password, and only where the verifier is known to use a slow hash and to rate limit. Where you do not know, generate 16.

Doing this in your own code

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

function randomString(length = 12) {
  // 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();  // 12 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(12))

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(12)

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(12);

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(12);
for (int i = 0; i < 12; 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[12];
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 12; echo

# openssl, if you want 12 hexadecimal characters instead
# (6 bytes = 12 hex characters = 48 bits, exactly)
openssl rand -hex 6

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

Is a 12-character random password strong enough?

At 71.45 bits it is above the 64-bit level NIST attaches rate limiting to, and it meets the PCI DSS v4.0.1 minimum of 12. It does not meet NIST SP 800-63B-4, which sets a 15-character SHALL for single-factor authentication. For a password manager entry, generate 16 or more; for something you type daily under a slow hash, 12 is defensible.

Why do PCI DSS and NIST disagree?

They target different things. PCI DSS v4.0.1 requirement 8.3.6 sets 12 characters plus a numeric and alphabetic class check for cardholder data environments. NIST SP 800-63B-4 sets 15 characters for single-factor use and forbids composition rules on the grounds that they push users toward predictable patterns. A machine-generated string sidesteps the objection, since the draw is uniform either way.

How long would it take to crack 71.5 bits?

About 232 years of average search against unsalted MD5 on one GPU, about 20 hours on a hundred thousand of them, and on the order of ten billion years against bcrypt at cost 12. Naming the hash is not a technicality here; it moves the answer by more than twenty orders of magnitude.

Does requiring an uppercase, a lowercase and a digit make it stronger?

No, it makes it very slightly weaker, because it removes valid strings from the space. The effect at this length is a fraction of a bit and the tool shows the corrected figure. Impose the constraint when a validator forces you to, not because it improves anything.

Should I use a passphrase instead?

If a human has to type or remember it, often yes. Five words drawn uniformly from a 7,776-word list is about 64.6 bits and far easier to retype accurately than 12 random characters. If the value lives in a password manager and is only ever pasted, the random string is the better choice.

Related generators