16-character password generator

Sixteen characters clears every published minimum and fits inside every common maximum. It is the sensible default.

G
More options
up to 10,000

Constraints

Sixteen clears every published minimum

NIST SP 800-63B-4 requires 15 characters for a single-factor password, as a SHALL. PCI DSS v4.0.1 requirement 8.3.6 requires 12 with numeric and alphabetic characters. OWASP asks for 15. Sixteen characters satisfies all three with a character in hand, and it fits comfortably inside every maximum in common use: 256 in Microsoft Entra ID, 128 in the AWS IAM default policy, 127 for interactive logon in Active Directory. That combination is why 16 is the length most likely to work without an argument.

On the 74-character default alphabet, 16 characters gives 99.35 bits. With all four classes required, the exact figure is 99.11 bits, a cost of 0.24 bits. Both land in the 80 to 111 bit band, which is where session tokens and most API keys sit and where brute force stops being a realistic line of attack against any modern password hash.

The next two thresholds are worth knowing if you are choosing a length deliberately. NIST specifies 112 bits as the minimum security strength for random bit generation, via SP 800-131A. On this alphabet the first length that clears 112 bits is 19 characters, and the first that reaches 128 bits, matching AES-128, is 21. Above 128 bits, extra length buys nothing at all against brute force, because the search is already impossible.

Sixteen characters, by alphabet
AlphabetNBits per characterTotal at length 16
Lowercase only264.700475.21
Crockford base32325.000080.00
Alphanumeric625.954295.27
Alphanumeric plus 12 safe symbols746.209599.35
Printable ASCII except space946.5546104.87

The 6.0% that makes class enforcement necessary

A 16-character alphanumeric password is the single most common shape a generator produces, and it has a specific failure mode against PCI DSS requirement 8.3.6, which demands both numeric and alphabetic characters. The probability that a uniform 16-character draw from the 62-character alphanumeric alphabet contains no digit at all is (52/62) raised to the 16th power. That is 0.05995, or 6.0%. Around one password in every seventeen would be rejected by a compliant checker despite being perfectly strong.

Widen the check to all four classes on the 74-character alphabet and the failure rate rises to 15.5% of uniform draws. Neither number is a defect in the randomness. They are exactly what uniformity produces, and they are the reason a compliance mode has to enforce a constraint rather than trust the odds.

The cost of enforcement is measurable and small. Requiring all four classes at length 16 reduces the true entropy from 99.35 to 99.11 bits and rejects 15.5% of candidate draws, giving an expected 1.18 attempts per password. This tool shows the corrected figure rather than the flattering one. A generator that displays 99.35 while enforcing a constraint is overstating its own output, and a generator that enforces the constraint by overwriting fixed positions is overstating it by more than the arithmetic suggests, because the result is no longer uniform.

Requiring a class always lowers entropy. It cannot raise it. A rule that removes possible outputs makes the output space smaller, and any tool that shows a higher number when you tick the box has the sign wrong.

Sixteen characters in practice

This length is short enough to type occasionally and long enough that you should not have to. Use it for anything a password manager holds: account logins, database credentials, service accounts, application secrets, and the many web forms that cap at 20 or 32 characters without telling you.

Watch for the systems that silently truncate rather than reject. If a field accepts 16 characters and stores only the first 12, the entropy of the stored secret is 74.5 bits and not 99.4, and nothing in the interface will tell you. The way to catch it is to log in once with the full value and once with a truncated prefix before you rely on the account.

Where the password is machine to machine rather than human facing, a symbol alphabet is usually the wrong tool. A 32-character hexadecimal string is exactly 128 bits and contains nothing a shell, a URL or a configuration parser will reinterpret. Reach for a password alphabet when a person or a legacy login field is in the loop.

Doing this in your own code

JavaScript
// 74-character alphabet: A-Z a-z 0-9 and the 12 safe symbols.
const ALPHABET =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%*+-=?@^_";

// Rejection sampling. v % n on a raw uint32 is biased whenever n does not
// divide 2^32, and 74 does not.
function pick(n) {
  const limit = Math.floor(2 ** 32 / n) * n;
  const buf = new Uint32Array(1);
  let v;
  do {
    crypto.getRandomValues(buf);
    v = buf[0];
  } while (v >= limit);
  return v % n;
}

function password(length = 20) {
  let out = "";
  for (let i = 0; i < length; i++) out += ALPHABET[pick(ALPHABET.length)];
  return out;
}

Math.random() is never acceptable here. V8 implements it with xorshift128+, whose internal state is recoverable from a handful of outputs.

Python 3.6+
import secrets
import string

ALPHABET = string.ascii_letters + string.digits + "!#$%*+-=?@^_"

def password(length: int = 20) -> str:
    return "".join(secrets.choice(ALPHABET) for _ in range(length))

# With a required-class check, resample the WHOLE string on failure.
def password_all_classes(length: int = 20) -> str:
    while True:
        p = password(length)
        if (any(c.isupper() for c in p) and any(c.islower() for c in p)
                and any(c.isdigit() for c in p)
                and any(c in "!#$%*+-=?@^_" for c in p)):
            return p

secrets.choice draws through randbelow, which rejection samples. random.choice rejection samples too, but it runs on the Mersenne Twister and must not be used for a password.

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

const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%*+-=?@^_"

func Password(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
}

crypto/rand.Int rejection samples internally. math/rand is seeded and predictable, and importing the wrong one is a common review miss.

PHP 7+
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%*+-=?@^_';
$n = strlen($alphabet);

$password = '';
for ($i = 0; $i < 20; $i++) {
    $password .= $alphabet[random_int(0, $n - 1)];
}

random_int throws rather than falling back to a weak source. rand() and mt_rand() are not suitable for credentials.

Java
import java.security.SecureRandom;

static final String ALPHABET =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%*+-=?@^_";

static String password(int length) {
    SecureRandom rnd = new SecureRandom();
    StringBuilder sb = new StringBuilder(length);
    for (int i = 0; i < length; i++) {
        sb.append(ALPHABET.charAt(rnd.nextInt(ALPHABET.length())));
    }
    return sb.toString();
}

nextInt(bound) rejection samples. Do not build one from nextDouble or from nextInt() % n.

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

const string Alphabet =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%*+-=?@^_";

string Password(int length) =>
    new string(RandomNumberGenerator.GetItems<char>(Alphabet, length));

RandomNumberGenerator.GetItems arrived in .NET 8 and is unbiased. On older targets, loop over GetInt32(0, Alphabet.Length).

Shell
# tr discards every byte outside the set, which is rejection sampling by
# construction. The hyphen must stay last inside the bracket.
LC_ALL=C tr -dc 'A-Za-z0-9!#$%*+=?@^_-' < /dev/urandom | head -c 20; echo

# A 192-bit token, if you do not need a typeable password. 24 bytes is
# 32 base64 characters with no padding, in the standard +/ alphabet.
openssl rand -base64 24

Without LC_ALL=C, tr in a UTF-8 locale can behave differently on high bytes.

Questions

Is a 16-character password strong enough?

Yes for essentially any account. It is 99.11 bits with all four classes required, above the NIST minimum length of 15, and beyond brute force against any password hash in current use. The realistic threats to it are phishing, reuse across sites, and malware on the machine that holds it, none of which more length would fix.

Why is the entropy 99.11 and not 99.35?

Because the four-class constraint is on. Forbidding strings that miss a class shrinks the output space, so the true entropy is log2 of the count of valid strings rather than 16 times log2(74). The difference is 0.24 bits. Turn the constraint off and the tool shows 99.35.

Should I use 16 or 20 characters?

If a password manager stores it and you never type it, use 20 or more, since the extra length is free. Use 16 when you occasionally have to type it, or when the field is capped. The security difference between them is not the reason to choose one, since both are far past the point of brute force being viable.

Do I need symbols in a 16-character password?

Not for strength. Dropping from the 74-character alphabet to plain alphanumeric moves 16 characters from 99.35 to 95.27 bits, and 95 bits is not meaningfully more crackable than 99. Symbols matter when a system demands them. If a symbol keeps getting rejected, use the alphanumeric set and add a character instead.

Is this the same as a 128-bit key?

No. Sixteen characters here is 99.35 bits, not 128, because each character carries 6.209 bits rather than 8. Reaching 128 bits on this alphabet takes 21 characters. Sixteen bytes of raw randomness, rendered as 32 hexadecimal characters, is exactly 128 bits.

Related generators