20-character password generator

Twenty characters is 124.2 bits, past the point where any brute force argument matters. The limits are practical now.

G
More options
up to 10,000

Constraints

What 124.2 bits means

Twenty characters from the 74-character default alphabet gives 124.19 bits, or 124.06 bits with all four character classes required. That is past 112 bits, which is the minimum security strength NIST SP 800-63B-4 mandates for random bit generation by reference to SP 800-131A, so a secret of this size is inside the tier that standard treats as compliant for cryptographic use.

One more character would take it to 130.4 bits, which is where this alphabet first passes 128 bits and matches the strength of AES-128. There is no meaningful ceiling above that: the strength scale stops at 128 because beyond it, brute force has already been ruled out by physics rather than by margin, and additional length only buys resistance to attacks that were never going to be brute force in the first place.

Which means the interesting question at this length is no longer arithmetic. Every real risk to a 20-character password lives somewhere else: reuse across services, a phishing page that captures it in full, a keylogger, a breach at a site that stored it badly, or a recovery flow that bypasses the password entirely. Length has stopped being the variable.

Where each length sits on the 74-character alphabet
LengthBits, unconstrainedBits, all four classes requiredBand
849.6848.54Weak, rate limiting required below 64 bits
1274.5174.02Moderate
1699.3599.11Strong
20124.19124.06Very strong, past the 112-bit NIST strength
21130.40130.29Cryptographic, at or above AES-128

Long passwords break things

At twenty characters and above, the failure mode stops being cryptographic and becomes operational. Systems impose maximum lengths and character bans, and the badly behaved ones truncate rather than refuse. Microsoft Entra ID takes 256 characters but rejects Unicode outright. Active Directory allows 127 for interactive logon. The AWS IAM default policy caps at 128. Google requires ASCII and rejects a leading or trailing space, which is easy to introduce with a sloppy copy and paste.

The symbol set is the other common source of rejection. This tool defaults to the twelve symbols !#$%*+-=?@^_ because every one of them appears in the Entra ID, Active Directory and OWASP allowed sets, so a password made from them tends to be accepted everywhere. That restriction costs 5.3% of the per-character entropy against the full printable ASCII punctuation set, which a single extra character more than recovers. Note that AWS IAM does not list the question mark among its accepted non-alphanumeric characters, so an AWS-specific set is eleven symbols across 73 characters, at 6.1898 bits each.

Two more hazards belong to how the password travels rather than how it was made. A plus sign in an unencoded URL query string decodes to a space, so a password sent through a link can arrive different from the one generated. And a password starting with an equals sign, a plus, a hyphen or an at sign is treated as a formula when a CSV file is opened in a spreadsheet, which corrupts the value and is a real injection route besides.

  • Verify a long password by logging out and back in once, before you close the tab that generated it.
  • If a field caps below your length without saying so, the stored secret is the prefix, and its entropy is the entropy of that prefix.
  • Store it in a password manager. Twenty random characters is not a value anyone should be planning to remember.

Rotation, and why this length does not need it

NIST SP 800-63B-4 removes periodic expiry. Passwords are to be changed on evidence of compromise, not on a schedule, because scheduled changes reliably produce small predictable edits to the previous value. That reasoning applies with particular force to a machine-generated 20-character password, which has no relationship to its predecessor and gains nothing from being replaced with another one of the same size.

The exceptions are worth naming. PCI DSS v4.0.1 still requires 90-day rotation for in-scope accounts without multi-factor authentication, so a regulated environment may not have the choice. Break-glass and shared administrative credentials should be rotated after every use, since the population who knows the value is exactly the point. And any credential should be rotated immediately when someone with access leaves, or when a service you used it on discloses a breach.

The other thing worth doing at this length is checking the value against breached-password lists, which NIST does require. A generated password will never appear on one, and the check costs nothing, but it matters for the passwords already in your estate that were not generated this way.

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 20 characters overkill?

For brute-force resistance, yes, and openly so: 124.06 bits is far past the point of being searchable. It is not overkill in cost, because a password manager types it for you. Choose 20 when nothing forces you shorter, and 16 when a field caps you or you have to type it.

Should I go past 20 characters?

There is no brute-force reason to. At 21 characters this alphabet reaches 130.4 bits, matching AES-128, and past that extra length defends against nothing that a shorter random password does not already defend against. Longer values are also more likely to hit a truncating field.

Can I remember a 20-character random password?

You should not try. A uniform random string has no structure to hang memory on, which is precisely why it is strong. If you need something a person can carry in their head, use a passphrase of uniformly chosen words instead and accept the extra length that requires.

Why does the site reject my 20-character password?

Almost always a maximum length or a banned character rather than the length itself. Try the alphanumeric set with no symbols first, since symbol bans are the most common cause, and check whether the field silently accepted a truncated version. Some systems also reject a leading or trailing space introduced by copy and paste.

Does a longer password protect me from phishing?

No. A phishing page captures whatever you type regardless of its length, and so does a keylogger. Those attacks are addressed by a password manager that will not autofill on the wrong domain, and by phishing-resistant multi-factor authentication. Length only defends against guessing.

Related generators