X Xerobit

Password Generator — How to Build a Secure Random Password Generator

Build a cryptographically secure password generator in JavaScript and Python. Learn character set composition, entropy calculation, browser crypto.getRandomValues, and how to...

Mian Ali Khalid · · 5 min read
Use the tool
Password Generator
Generate strong random passwords with configurable length, character classes, and exclusions. Real entropy meter, crack-time estimate, bulk mode.
Open Password Generator →

A secure password generator must use a cryptographically random source — never Math.random(). Here’s how to build one in JavaScript and Python with proper entropy.

Use the Password Generator to generate secure passwords instantly.

Why not Math.random()

// WRONG — Math.random() is not cryptographically secure:
function badPassword(length) {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}

// Math.random() is a pseudo-random number generator (PRNG)
// Its output can be predicted with enough samples

Cryptographically secure in the browser

function generatePassword(length = 16, options = {}) {
  const {
    uppercase = true,
    lowercase = true,
    digits = true,
    symbols = true,
    excludeAmbiguous = false,  // l, 1, I, O, 0
  } = options;
  
  let chars = '';
  if (uppercase) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  if (lowercase) chars += 'abcdefghijklmnopqrstuvwxyz';
  if (digits)    chars += '0123456789';
  if (symbols)   chars += '!@#$%^&*()_+-=[]{}|;:,.<>?';
  
  if (excludeAmbiguous) {
    chars = chars.replace(/[lI1O0]/g, '');
  }
  
  if (!chars) throw new Error('At least one character type required');
  
  const array = new Uint32Array(length);
  crypto.getRandomValues(array);  // CSPRNG
  
  return Array.from(array, n => chars[n % chars.length]).join('');
}

// Usage:
generatePassword(20)
// 'k4$Hm9pL#rN2xQjW7@Yv'

generatePassword(16, { uppercase: true, digits: true, symbols: false })
// 'kHmpLrNxQjWvBcDe'

Avoid modulo bias

The n % chars.length approach introduces a slight bias — values near 0 are slightly more likely than others. For maximum security, reject values that would cause bias:

function unbiasedPassword(length, chars) {
  const array = new Uint8Array(length * 2);
  const result = [];
  
  const limit = 256 - (256 % chars.length); // Max value without bias
  let i = 0;
  
  while (result.length < length) {
    crypto.getRandomValues(array.subarray(i, i + 1));
    if (array[i] < limit) {
      result.push(chars[array[i] % chars.length]);
    }
    i++;
    if (i >= array.length) {
      i = 0;  // Refill buffer
    }
  }
  
  return result.join('');
}

Python: secrets module

import secrets
import string

def generate_password(
    length: int = 16,
    uppercase: bool = True,
    lowercase: bool = True,
    digits: bool = True,
    symbols: bool = True,
) -> str:
    chars = ''
    if uppercase: chars += string.ascii_uppercase
    if lowercase: chars += string.ascii_lowercase
    if digits:    chars += string.digits
    if symbols:   chars += '!@#$%^&*()_+-=[]{}|;:,.<>?'
    
    if not chars:
        raise ValueError('At least one character type required')
    
    # secrets.choice uses os.urandom() — cryptographically secure
    return ''.join(secrets.choice(chars) for _ in range(length))

# Ensure all required character types are present:
def generate_strong_password(length: int = 16) -> str:
    while True:
        password = generate_password(length)
        if (any(c.isupper() for c in password) and
            any(c.islower() for c in password) and
            any(c.isdigit() for c in password) and
            any(c in '!@#$%^&*' for c in password)):
            return password

Entropy calculation

Entropy determines how hard a password is to brute-force:

function calculateEntropy(password) {
  const charsets = [
    { test: /[a-z]/, size: 26, name: 'lowercase' },
    { test: /[A-Z]/, size: 26, name: 'uppercase' },
    { test: /[0-9]/, size: 10, name: 'digits' },
    { test: /[^a-zA-Z0-9]/, size: 32, name: 'symbols' },
  ];
  
  const poolSize = charsets.reduce((size, { test }) => 
    test.test(password) ? size + charsets.find(c => c.test === test).size : size
  , 0);
  
  // Entropy = log2(poolSize^length)
  const entropy = password.length * Math.log2(poolSize);
  
  return {
    entropy: Math.round(entropy),
    bits: `${Math.round(entropy)} bits`,
    strength: entropy < 40 ? 'Weak' : entropy < 60 ? 'Fair' : entropy < 80 ? 'Good' : 'Excellent',
  };
}

calculateEntropy('password')     // { entropy: 37, strength: 'Weak' }
calculateEntropy('P@ssw0rd!')    // { entropy: 52, strength: 'Fair' }
calculateEntropy('k4$Hm9pL#rN') // { entropy: 72, strength: 'Good' }

Pronounceable password (alternative)

function generatePronounceable(length = 16) {
  const consonants = 'bcdfghjklmnprstvwxyz';
  const vowels = 'aeiou';
  const digits = '0123456789';
  
  let result = '';
  const arr = new Uint8Array(length * 2);
  crypto.getRandomValues(arr);
  let i = 0;
  
  while (result.length < length) {
    const isConsonant = result.length % 2 === 0;
    const set = isConsonant ? consonants : vowels;
    result += set[arr[i] % set.length];
    i++;
  }
  
  // Add a digit and capitalize first letter:
  return result.charAt(0).toUpperCase() + result.slice(1) + digits[arr[i] % digits.length];
}

generatePronounceable()  // 'Balexomipuratef3'

Related posts

Related tool

Password Generator

Generate strong random passwords with configurable length, character classes, and exclusions. Real entropy meter, crack-time estimate, bulk mode.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.