DEV Community

VaultKeepR
VaultKeepR

Posted on

Password Generator Best Practices: Build Truly Secure Tools

Cover

Your users' accounts get breached because their passwords suck. Even in 2024, "123456" and "password" still top the most common password lists. But here's the kicker: most password generators—including popular browser extensions—generate passwords that are mathematically weaker than they appear.

Why Password Generation Actually Matters Now

The threat landscape has evolved. Attackers now use GPU clusters that can crack 350 billion password combinations per second. Rainbow tables have expanded to cover common password patterns. And with the rise of credential stuffing attacks affecting 193 billion login attempts annually, weak password generation isn't just a UX problem—it's an existential threat to your application's security.

The problem? Most developers implement password generation as an afterthought, using Math.random() or predictable algorithms that create an illusion of security while leaving users vulnerable.

The Science of Cryptographically Secure Password Generation

True password security comes down to entropy—the measure of randomness in your generated passwords. But entropy isn't just about length or character sets; it's about the unpredictability of your random source.

Entropy Calculation

function calculateEntropy(characterSet: number, length: number): number {
  return Math.log2(Math.pow(characterSet, length));
}

// Example: 12-character password with uppercase, lowercase, numbers, symbols
const entropy = calculateEntropy(94, 12); // ~78.8 bits
console.log(`Password entropy: ${entropy.toFixed(1)} bits`);
Enter fullscreen mode Exit fullscreen mode

For reference:

  • < 28 bits: Crackable in seconds
  • 28-35 bits: Weak, crackable in minutes
  • 36-59 bits: Reasonable for low-value accounts
  • 60+ bits: Strong for high-value accounts
  • 128+ bits: Cryptographically secure

The Randomness Problem

Here's where most implementations fail:

// ❌ NEVER do this - predictable and weak
function badPasswordGenerator(length: number): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
  let result = '';
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return result;
}

// ✅ Cryptographically secure approach
function securePasswordGenerator(length: number): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
  const array = new Uint8Array(length);

  // Use cryptographically secure random number generator
  if (typeof window !== 'undefined' && window.crypto) {
    window.crypto.getRandomValues(array);
  } else if (typeof require !== 'undefined') {
    require('crypto').randomFillSync(array);
  } else {
    throw new Error('No cryptographically secure random source available');
  }

  return Array.from(array, byte => chars[byte % chars.length]).join('');
}
Enter fullscreen mode Exit fullscreen mode

The key difference: Math.random() uses a pseudorandom number generator that's predictable if you know the seed. Cryptographic RNGs use entropy from hardware sources like mouse movements, keyboard timing, and thermal noise.

Advanced Password Generation Patterns

Diceware Method Implementation

For maximum security, implement the diceware method digitally:

interface DicewareConfig {
  wordCount: number;
  separator: string;
  includeNumbers: boolean;
  capitalizeWords: boolean;
}

class DicewareGenerator {
  private wordlist: string[];

  constructor(wordlist: string[]) {
    if (wordlist.length !== 7776) { // 6^5 combinations
      throw new Error('Diceware wordlist must contain exactly 7776 words');
    }
    this.wordlist = wordlist;
  }

  generate(config: DicewareConfig): string {
    const words: string[] = [];
    const array = new Uint32Array(config.wordCount);

    crypto.getRandomValues(array);

    for (let i = 0; i < config.wordCount; i++) {
      const index = array[i] % 7776;
      let word = this.wordlist[index];

      if (config.capitalizeWords) {
        word = word.charAt(0).toUpperCase() + word.slice(1);
      }

      words.push(word);
    }

    let passphrase = words.join(config.separator);

    if (config.includeNumbers) {
      const numberArray = new Uint8Array(1);
      crypto.getRandomValues(numberArray);
      passphrase += (numberArray[0] % 100).toString().padStart(2, '0');
    }

    return passphrase;
  }
}

// Usage: "Correct-Horse-Battery-Staple-42" style passwords
// 4 words = ~51 bits entropy, 6 words = ~77 bits
Enter fullscreen mode Exit fullscreen mode

Pronounceable Password Generation

For better user adoption, generate pronounceable passwords:

class PronounceableGenerator {
  private consonants = 'bcdfghjklmnpqrstvwxyz';
  private vowels = 'aeiou';
  private numbers = '0123456789';
  private symbols = '!@#$%^&*';

  generateSyllable(): string {
    const consonant = this.getSecureRandom(this.consonants);
    const vowel = this.getSecureRandom(this.vowels);
    const ending = Math.random() > 0.5 ? this.getSecureRandom(this.consonants) : '';

    return consonant + vowel + ending;
  }

  generate(syllables: number, includeNumbers: boolean = true, includeSymbols: boolean = true): string {
    let password = '';

    for (let i = 0; i < syllables; i++) {
      password += this.generateSyllable();
      if (i < syllables - 1 && Math.random() > 0.7) {
        password += this.getSecureRandom(this.numbers);
      }
    }

    if (includeNumbers) {
      password += this.getSecureRandom(this.numbers);
    }

    if (includeSymbols) {
      password += this.getSecureRandom(this.symbols);
    }

    return password;
  }

  private getSecureRandom(charset: string): string {
    const array = new Uint8Array(1);
    crypto.getRandomValues(array);
    return charset[array[0] % charset.length];
  }
}

// Generates passwords like: "kolex9!", "bitage4@", "morun7#"
Enter fullscreen mode Exit fullscreen mode

How VaultKeepR Implements Enterprise-Grade Generation

VaultKeepR's password generator combines multiple entropy sources and generation methods:

  1. Multi-source entropy: Hardware RNG, user interaction timing, and environmental noise
  2. Configurable algorithms: Traditional random, diceware, and pronounceable methods
  3. Real-time entropy estimation: Shows users actual security bits, not just "strong/weak"
  4. Zero-knowledge architecture: Passwords never leave the user's device unencrypted

The generator also validates against common patterns and known breaches before presenting passwords to users, ensuring generated credentials haven't appeared in credential dumps.

Implementation Checklist for Developers

Essential Requirements:

  • [ ] Use crypto.getRandomValues() or equivalent cryptographic RNG
  • [ ] Calculate and display actual entropy bits
  • [ ] Support multiple character sets (avoid ambiguous characters like 0/O, 1/l)
  • [ ] Implement minimum entropy thresholds (60+ bits for sensitive accounts)
  • [ ] Add breach database checking before password acceptance

Advanced Features:

  • [ ] Multiple generation algorithms (random, diceware, pronounceable)
  • [ ] Configurable complexity rules
  • [ ] Export functionality for password managers
  • [ ] Rate limiting to prevent brute force enumeration of your generator

Security Hardening:

interface PasswordPolicy {
  minLength: number;
  minEntropy: number;
  requireUppercase: boolean;
  requireLowercase: boolean;
  requireNumbers: boolean;
  requireSymbols: boolean;
  excludeAmbiguous: boolean;
  excludeDictionary: boolean;
  excludePersonalInfo: boolean;
}

function validatePassword(password: string, policy: PasswordPolicy): ValidationResult {
  const checks = {
    length: password.length >= policy.minLength,
    entropy: calculateEntropy(getCharacterSetSize(password), password.length) >= policy.minEntropy,
    uppercase: policy.requireUppercase ? /[A-Z]/.test(password) : true,
    lowercase: policy.requireLowercase ? /[a-z]/.test(password) : true,
    numbers: policy.requireNumbers ? /[0-9]/.test(password) : true,
    symbols: policy.requireSymbols ? /[^A-Za-z0-9]/.test(password) : true,
    // Additional validations...
  };

  return {
    valid: Object.values(checks).every(Boolean),
    checks,
    score: calculatePasswordScore(password, policy)
  };
}
Enter fullscreen mode Exit fullscreen mode

The Future of Password Generation

Password generation is evolving toward passkey adoption and account abstraction, but traditional passwords aren't disappearing soon. The future lies in:

  • Hybrid approaches: Combining traditional passwords with hardware tokens and biometrics
  • Context-aware generation: Passwords that adapt to threat levels and account sensitivity
  • Quantum-resistant algorithms: Preparing for post-quantum cryptography
  • Seamless UX: Making secure generation so easy that users never choose weak passwords

Smart contract wallets and decentralized identity are pushing password managers toward seed phrase generation and recovery mechanisms, requiring developers to understand BIP-39 and Shamir Secret Sharing alongside traditional password security.

The best password generator is the one users actually use. By implementing cryptographically secure generation with intuitive UX, you're building the foundation for your application's long-term security posture. Your users' digital lives depend on getting this right.

Top comments (0)