8-character password generator

Eight characters is below the current NIST minimum. Here is exactly what it buys you and when it is still unavoidable.

G
More options
up to 10,000

Constraints

Eight characters is below the current minimum

NIST SP 800-63B-4 requires a minimum of 15 characters for a password used as a single authentication factor, and it states that as a SHALL rather than a recommendation. Eight characters is permitted by the same document only when the password is one factor inside multi-factor authentication. If this password is the only thing protecting the account, it does not meet the standard, and a strength meter that colours it green is lying to you.

The measured numbers say the same thing. Eight characters drawn from the 74-character alphabet used here gives 49.68 bits. With all four character classes required, the true figure falls to 48.54 bits, because the constraint forbids strings and shrinks the output space. Both sit inside the 32 to 63 bit band, and NIST SP 800-63B-4 states that a secret below 64 bits SHALL be rate limited by the verifier. NIST itself does not treat a secret this size as safe standing alone.

That is not an argument for adding more symbols. Even the full 94-character printable ASCII alphabet gives only 52.44 bits at length 8. The limiting factor is the length, and no alphabet choice rescues it.

Eight characters, by alphabet
AlphabetNBits per characterTotal at length 8
Lowercase only264.700437.60
Letters, mixed case525.700445.60
Alphanumeric625.954247.63
Alphanumeric plus 12 safe symbols746.209549.68
Printable ASCII except space946.554652.44

What 49.7 bits costs an attacker

These figures assume the attacker has the password hash and knows the alphabet and length, which is the assumption you should make about anything already stolen. The rates are measured, from a hashcat 6.2.6 benchmark on one NVIDIA RTX 5090. Expected work to find the password is half the keyspace, so the times below are 2 to the power 47.5 divided by the rate.

The spread across the table is the entire point. The same 8-character password is sixteen minutes of work behind MD5 and decades behind bcrypt on identical hardware, and you do not get to choose which one the site used. On a large cluster the MD5 figure collapses further: at a hundred thousand GPUs the same password falls in under ten milliseconds.

48.54 bits, one RTX 5090, hashcat 6.2.6 measured rates
Stored asRateExpected time to find
MD5220.6 GH/s16 minutes
NTLM340.1 GH/s10 minutes
SHA2-25628.35 GH/s2 hours
bcrypt, cost 5304.8 kH/s21 years
scrypt, N=163847,760 H/s838 years

The bcrypt and scrypt columns are why a short password sometimes survives a breach. They are not a reason to choose a short password, because the hashing decision belongs to the service, not to you, and you usually cannot find out what it made.

When eight characters is forced on you

Plenty of systems still cap or floor at eight, and no amount of standards citation changes what a field will accept. Windows Active Directory with the default Passfilt policy has a seven-character minimum and a three-of-five category rule. The AWS IAM default password policy sets eight. GitHub accepts eight if the password contains a number and a lowercase letter, and anything at all if it is fifteen or longer. Older appliance firmware, mainframe terminals and industrial control interfaces frequently have fixed-width credential fields that predate all of this.

If you are stuck at eight, the mitigations that actually matter are the ones outside the password. Enable multi-factor authentication, which is the specific condition under which NIST permits eight characters in the first place. Confirm the system rate limits failed attempts, since that is what NIST requires below 64 bits. Never reuse the value anywhere else, because a short password that leaks from one system is a short password everywhere it was reused. And treat the account as higher risk than its length suggests when you are deciding what it may reach.

The class constraint costs more at this length than anywhere else on the site: 1.13 bits, and 54.4% of candidate draws are rejected and regenerated. It is still the right implementation if the target system demands all four classes, because the alternative, placing one character from each class at fixed positions, is not uniform and gives away more than 1.13 bits.

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 an 8-character password secure in 2026?

Not as a single factor. It is 48.54 bits under a four-class constraint, which is inside the band NIST SP 800-63B-4 requires verifiers to rate limit, and roughly sixteen minutes of work against an MD5 hash on one current GPU. It is acceptable in the narrow case NIST allows: as one factor of multi-factor authentication, on a system that rate limits.

Why does this page exist if it recommends against eight characters?

Because the requirement is often not yours to set. A field that accepts exactly eight characters is a fact about the system you have to use, and generating a uniform random eight beats inventing one. The page states the number rather than dressing it up.

Would adding more symbols make eight characters enough?

No. Moving from the 74-character default to the full 94-character printable ASCII set takes it from 49.68 to 52.44 bits, under three bits, which is roughly a factor of seven in attacker work against a problem that needs a factor of millions. Length is the only lever that changes the answer.

Is a random 8-character password better than a memorable one?

At the same length, yes, and by a wide margin, because a human-chosen eight-character password is not drawn uniformly and is heavily concentrated on a small set of patterns that cracking rules encode directly. The uniform draw is the only case where the bit count means what it says.

What should I use instead when I have the choice?

Sixteen characters gives 99.11 bits under the same four-class constraint, clears the NIST minimum of 15, and fits inside every maximum length in common use. If you are storing it in a password manager, twenty costs you nothing extra to use.

Related generators