Toolmingo
Guides7 min read

How to Generate a Random Password Programmatically

Learn how to generate cryptographically secure random passwords in JavaScript, Python, Go, and PHP — with character sets, length, and entropy explained.

Generating a random password sounds simple — pick some random characters, done. But most naive implementations have a subtle flaw: they use a pseudorandom number generator (PRNG) that is predictable if an attacker knows its seed. For passwords, you need a cryptographically secure pseudorandom number generator (CSPRNG). Here's how to do it correctly in four languages.

The character set problem

A password's strength depends on two things: length and the size of the character pool it draws from. A password using only lowercase letters (26 characters) is much weaker than one using all printable ASCII (94 characters) at the same length.

Character class Pool size Example characters
Lowercase letters 26 abcdefghijklmnopqrstuvwxyz
Uppercase letters 26 ABCDEFGHIJKLMNOPQRSTUVWXYZ
Digits 10 0123456789
Common symbols 32 `!@#$%^&*()_+-=[]{}
All printable ASCII 94 all of the above

Entropy (bits) = length × log₂(pool size). A 16-character password from a 94-character pool has 16 × log₂(94) ≈ 104 bits of entropy — more than enough against brute force.

Why Math.random() is wrong for passwords

Math.random() and equivalent functions in other languages (Python's random.choice, PHP's rand()) use a seeded PRNG whose internal state can be inferred from a few outputs. They are designed for statistical randomness, not security. Always use the OS-provided CSPRNG for anything credential-related.

JavaScript

Browser

function generatePassword(length = 16, options = {}) {
  const useLower = options.lowercase ?? true;
  const useUpper = options.uppercase ?? true;
  const useDigits = options.digits ?? true;
  const useSymbols = options.symbols ?? false;

  let charset = '';
  if (useLower) charset += 'abcdefghijklmnopqrstuvwxyz';
  if (useUpper) charset += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  if (useDigits) charset += '0123456789';
  if (useSymbols) charset += '!@#$%^&*()_+-=[]{}|;:,.<>?';

  if (!charset) throw new Error('At least one character class must be selected');

  const array = new Uint32Array(length);
  crypto.getRandomValues(array); // CSPRNG — always available in browser and Node 19+

  return Array.from(array, (n) => charset[n % charset.length]).join('');
}

// Usage
console.log(generatePassword(20, { symbols: true }));
// e.g. "aK9#mPx2@LqRt5!vNj8&"

crypto.getRandomValues is the Web Crypto API — available in all modern browsers and Node.js 19+ without importing anything. It reads from the OS entropy pool.

Node.js (older versions)

const { randomInt } = require('crypto'); // Node built-in

function generatePassword(length = 16, charset = null) {
  charset ??= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  return Array.from({ length }, () => charset[randomInt(charset.length)]).join('');
}

crypto.randomInt(max) returns a cryptographically secure integer in [0, max).

Python

Python's secrets module (added in 3.6) is specifically designed for security-sensitive random generation.

import secrets
import string

def generate_password(
    length: int = 16,
    lowercase: bool = True,
    uppercase: bool = True,
    digits: bool = True,
    symbols: bool = False,
) -> str:
    charset = ''
    if lowercase:
        charset += string.ascii_lowercase
    if uppercase:
        charset += string.ascii_uppercase
    if digits:
        charset += string.digits
    if symbols:
        charset += string.punctuation

    if not charset:
        raise ValueError('At least one character class must be selected')

    return ''.join(secrets.choice(charset) for _ in range(length))

# Guarantee at least one character from each required class
def generate_password_guaranteed(length: int = 16) -> str:
    required = [
        secrets.choice(string.ascii_lowercase),
        secrets.choice(string.ascii_uppercase),
        secrets.choice(string.digits),
        secrets.choice('!@#$%^&*'),
    ]
    charset = string.ascii_letters + string.digits + '!@#$%^&*'
    rest = [secrets.choice(charset) for _ in range(length - len(required))]
    combined = required + rest
    secrets.SystemRandom().shuffle(combined)  # shuffle without bias
    return ''.join(combined)

print(generate_password(20, symbols=True))
print(generate_password_guaranteed(20))

secrets.choice reads from os.urandom() under the hood. Never use random.choice for passwords — that module is seeded and predictable.

Go

package main

import (
    "crypto/rand"
    "fmt"
    "math/big"
    "strings"
)

const (
    lowerChars  = "abcdefghijklmnopqrstuvwxyz"
    upperChars  = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    digitChars  = "0123456789"
    symbolChars = "!@#$%^&*()_+-=[]{}|;:,.<>?"
)

type PasswordOptions struct {
    Length  int
    Lower   bool
    Upper   bool
    Digits  bool
    Symbols bool
}

func GeneratePassword(opts PasswordOptions) (string, error) {
    var charset strings.Builder
    if opts.Lower {
        charset.WriteString(lowerChars)
    }
    if opts.Upper {
        charset.WriteString(upperChars)
    }
    if opts.Digits {
        charset.WriteString(digitChars)
    }
    if opts.Symbols {
        charset.WriteString(symbolChars)
    }

    pool := charset.String()
    if len(pool) == 0 {
        return "", fmt.Errorf("at least one character class must be selected")
    }

    result := make([]byte, opts.Length)
    poolSize := big.NewInt(int64(len(pool)))

    for i := range result {
        n, err := rand.Int(rand.Reader, poolSize) // CSPRNG via crypto/rand
        if err != nil {
            return "", err
        }
        result[i] = pool[n.Int64()]
    }

    return string(result), nil
}

func main() {
    pwd, err := GeneratePassword(PasswordOptions{
        Length: 20, Lower: true, Upper: true, Digits: true, Symbols: true,
    })
    if err != nil {
        panic(err)
    }
    fmt.Println(pwd)
}

crypto/rand.Reader reads from /dev/urandom on Linux/macOS and BCryptGenRandom on Windows — both are CSPRNGs. Never use math/rand for passwords.

PHP

<?php

function generatePassword(
    int $length = 16,
    bool $lowercase = true,
    bool $uppercase = true,
    bool $digits = true,
    bool $symbols = false
): string {
    $charset = '';
    if ($lowercase) $charset .= 'abcdefghijklmnopqrstuvwxyz';
    if ($uppercase) $charset .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    if ($digits)    $charset .= '0123456789';
    if ($symbols)   $charset .= '!@#$%^&*()_+-=[]{}|;:,.<>?';

    if ($charset === '') {
        throw new \InvalidArgumentException('At least one character class must be selected');
    }

    $charsetLength = strlen($charset);
    $password = '';

    for ($i = 0; $i < $length; $i++) {
        // random_int() uses /dev/urandom or Windows CryptGenRandom — CSPRNG
        $password .= $charset[random_int(0, $charsetLength - 1)];
    }

    return $password;
}

echo generatePassword(20, true, true, true, true) . PHP_EOL;

random_int() was added in PHP 7.0 and uses the OS CSPRNG. Never use rand() or mt_rand() for passwords — both are predictable Mersenne Twister PRNGs.

Quick reference

Language CSPRNG function Avoid
JS (browser/Node 19+) crypto.getRandomValues() Math.random()
Node.js (older) crypto.randomInt() Math.random()
Python secrets.choice() random.choice()
Go crypto/rand.Int() math/rand
PHP random_int() rand(), mt_rand()
CLI (Linux/macOS) openssl rand -base64 24

Ensuring character-class requirements

Many systems require "at least one uppercase, one digit, one symbol." The cleanest approach is:

  1. Generate one character from each required class.
  2. Fill the rest randomly from the full charset.
  3. Shuffle the result to avoid predictable positions (the required characters should not always appear at the start).

The Python example above shows this pattern. The same logic applies in any language — the key step is the shuffle using a CSPRNG, not random.shuffle (Python) or rand() (PHP).

6 common mistakes

1. Using PRNG instead of CSPRNG. Math.random(), Python's random, rand(), and mt_rand() are all seeded and statistically predictable. Use the platform CSPRNG for anything security-related.

2. Modulo bias. n % charset.length introduces a subtle bias when UINT_MAX is not divisible by charset.length. For most practical character set sizes this is negligible, but crypto.randomInt() and secrets.choice() handle this correctly by default.

3. Predictable positions for required characters. If you always place the required digit at index 0, the output is partially predictable. Shuffle with a CSPRNG after assembling.

4. Storing the plaintext password. Your generator should produce and immediately display the password — it should not be stored anywhere in plaintext. Store only a bcrypt/Argon2 hash in your database.

5. Not validating the minimum length. Short passwords are weak regardless of character set. Enforce a minimum of 12 characters; 16–20 is better.

6. Trusting the client for server-side generation. If you generate a password server-side (e.g., for initial account setup), generate and hash it on the server, not in the browser. Browser-generated passwords sent over the wire can be intercepted if TLS is misconfigured.

6 frequently asked questions

How long should a generated password be? 16 characters with lowercase + uppercase + digits + symbols gives ~104 bits of entropy — well above the 80-bit threshold. 12 characters is acceptable as an absolute minimum; 20+ for high-value accounts.

Can I use UUID as a password? A UUID v4 has ~122 bits of entropy and looks like 550e8400-e29b-41d4-a716-446655440000. It's technically secure but hard to type. It also uses only hex characters plus dashes, which some systems don't accept. A proper 20-character random password is better.

Is a longer password with fewer character classes better than a shorter one with all classes? Length wins. A 20-character lowercase-only password has 20 × log₂(26) ≈ 94 bits of entropy. A 12-character full-charset password has 12 × log₂(94) ≈ 78 bits. Longer is always better — both length and character variety together are best.

What about passphrases (e.g., correct-horse-battery-staple)? Passphrases drawn from a large wordlist (e.g., EFF's 7776-word Diceware list) can be extremely secure and memorable. Four random words give ~51 bits; six words give ~77 bits. Use five or more words for security-critical accounts.

Should I exclude ambiguous characters like 0, O, l, 1? For passwords that users will type by hand, yes — excluding them reduces transcription errors. For generated passwords stored in a password manager (never typed), there's no reason to restrict the pool.

How do I test my generator? Run it 100,000 times and check character-class distribution with a frequency analysis. Each class should appear roughly proportional to its share of the charset. Any obvious pattern — like symbols always appearing in the first two positions — indicates a bias bug.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools