Random Password Generator: Best Practices & Features to Look For

Build Your Own Random Password Generator: Simple GuideA password generator creates passwords that are hard to guess by combining characters randomly according to chosen rules. Building your own random password generator is a great way to learn about security basics, randomness, and practical programming. This guide walks through design choices, security considerations, and step‑by‑step implementations in three common languages: Python, JavaScript (Node/browser), and Go. It also shows how to add features like entropy estimation, pronounceable passwords, and saving secure password policies.


Why build your own?

  • Learning: Understand randomness, character sets, and secure coding.
  • Customization: Tailor length, allowed characters, and rules to your needs.
  • Trust: Running code locally avoids trusting third‑party tools.

Note: For real-world, production or shared use prefer audited libraries and well‑tested password managers. This guide is educational and can be adapted for personal use.


Key design decisions

  1. Character sets

    • Lowercase letters: a–z
    • Uppercase letters: A–Z
    • Digits: 0–9
    • Symbols: !“#$%&‘()*+,-./:;<=>?@[]^_`{|}~
    • Exclude ambiguous characters (e.g., l, I, 0, O) optionally.
  2. Length

    • Minimum recommended: 12 characters for general use.
    • For highly sensitive accounts, prefer 16+.
  3. Randomness source

    • Use a cryptographically secure random number generator (CSPRNG).
    • Avoid standard nonsecure RNGs (e.g., rand(), Math.random()) for security‑sensitive passwords.
  4. Policies & constraints

    • Allow toggles for character type inclusion.
    • Optionally enforce at least one character from each selected category.
    • Optionally block repeating characters or sequences.
  5. Usability features

    • Human‑friendly (pronounceable) option for easier memorization.
    • Entropy estimate and strength meter.
    • Copy to clipboard and one‑time display in UIs.

Entropy primer (brief)

Entropy measures unpredictability. For a password of length L selecting uniformly from a set of size N, entropy ≈ L * log2(N). Example: 12 characters from 94 printable ASCII characters gives about 12 * log2(94) ≈ 78 bits, well above common recommendations.


Implementations

Below are simple, secure implementations in Python, JavaScript (Node/browser), and Go. Each uses a CSPRNG and supports configurable length and character sets.

Python (3.6+)

import secrets import string # Character sets LOWER = string.ascii_lowercase UPPER = string.ascii_uppercase DIGITS = string.digits SYMBOLS = string.punctuation  # adjust to remove ambiguous/undesired chars def build_charset(use_lower=True, use_upper=True, use_digits=True, use_symbols=True, exclude_ambiguous=False):     charset = ""     if use_lower:         charset += LOWER     if use_upper:         charset += UPPER     if use_digits:         charset += DIGITS     if use_symbols:         charset += SYMBOLS     if exclude_ambiguous:         for ch in "Il1O0":             charset = charset.replace(ch, "")     if not charset:         raise ValueError("At least one character class must be enabled")     return charset def generate_password(length=16, require_each_class=False, **charset_opts):     charset = build_charset(**charset_opts)     if require_each_class:         classes = []         if charset_opts.get("use_lower", True): classes.append(LOWER)         if charset_opts.get("use_upper", True): classes.append(UPPER)         if charset_opts.get("use_digits", True): classes.append(DIGITS)         if charset_opts.get("use_symbols", True): classes.append(SYMBOLS)         if len(classes) > length:             raise ValueError("Length too short to include required classes")         # Place one from each class first         pwd = [secrets.choice(cls) for cls in classes]         pwd += [secrets.choice(charset) for _ in range(length - len(pwd))]         # Shuffle securely         for i in range(len(pwd) - 1, 0, -1):             j = secrets.randbelow(i + 1)             pwd[i], pwd[j] = pwd[j], pwd[i]         return "".join(pwd)     else:         return "".join(secrets.choice(charset) for _ in range(length)) if __name__ == "__main__":     print(generate_password(16, require_each_class=True, use_symbols=True)) 

JavaScript (Node.js and modern browsers)

Note: Use crypto.getRandomValues in browsers and crypto.randomFillSync in Node.js.

// Node.js (also works in modern browsers with small change) const crypto = require('crypto'); const LOWER = 'abcdefghijklmnopqrstuvwxyz'; const UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const DIGITS = '0123456789'; const SYMBOLS = `!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~`; function buildCharset(options = {}) {   const { useLower = true, useUpper = true, useDigits = true, useSymbols = true, excludeAmbiguous = false } = options;   let cs = '';   if (useLower) cs += LOWER;   if (useUpper) cs += UPPER;   if (useDigits) cs += DIGITS;   if (useSymbols) cs += SYMBOLS;   if (excludeAmbiguous) cs = cs.replace(/[Il1O0]/g, '');   if (!cs) throw new Error('At least one character class must be enabled');   return cs; } function secureRandomInt(max) {   // returns int in [0, max)   const byteLen = Math.ceil(Math.log2(max) / 8);   if (byteLen === 0) return 0;   while (true) {     const buf = crypto.randomBytes(byteLen);     let val = 0;     for (let i = 0; i < buf.length; i++) val = (val << 8) + buf[i];     if (val < Math.floor(256 ** byteLen / max) * max) return val % max;   } } function generatePassword(length = 16, options = {}) {   const charset = buildCharset(options);   let pwd = '';   for (let i = 0; i < length; i++) {     const idx = secureRandomInt(charset.length);     pwd += charset[idx];   }   return pwd; } if (require.main === module) {   console.log(generatePassword(16, { useSymbols: true, excludeAmbiguous: true })); } 

For browsers replace secureRandomInt using crypto.getRandomValues.

Go

package main import ( 	"crypto/rand" 	"errors" 	"fmt" 	"math/big" ) var ( 	lower  = "abcdefghijklmnopqrstuvwxyz" 	upper  = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 	digits = "0123456789" 	symbols = `!"#$%&'()*+,-./:;<=>?@[]^_` + "`" + `{|}~` ) func buildCharset(useLower, useUpper, useDigits, useSymbols, excludeAmbiguous bool) (string, error) { 	cs := "" 	if useLower { cs += lower } 	if useUpper { cs += upper } 	if useDigits { cs += digits } 	if useSymbols { cs += symbols } 	if excludeAmbiguous { 		for _, r := range "Il1O0" { 			cs = stringReplaceAll(cs, string(r), "") 		} 	} 	if cs == "" { return "", errors.New("no character classes enabled") } 	return cs, nil } func stringReplaceAll(s, old, new string) string { 	for { 		i := indexOf(s, old) 		if i < 0 { break } 		s = s[:i] + new + s[i+len(old):] 	} 	return s } func indexOf(s, sub string) int { 	for i := range s { 		if len(s[i:]) >= len(sub) && s[i:i+len(sub)] == sub { 			return i 		} 	} 	return -1 } func secureRandomInt(max int) (int, error) { 	nBig, err := rand.Int(rand.Reader, big.NewInt(int64(max))) 	if err != nil { return 0, err } 	return int(nBig.Int64()), nil } func generatePassword(length int, useLower, useUpper, useDigits, useSymbols, excludeAmbiguous bool) (string, error) { 	cs, err := buildCharset(useLower, useUpper, useDigits, useSymbols, excludeAmbiguous) 	if err != nil { return "", err } 	b := make([]byte, length) 	for i := 0; i < length; i++ { 		idx, err := secureRandomInt(len(cs)) 		if err != nil { return "", err } 		b[i] = cs[idx] 	} 	return string(b), nil } func main() { 	p, err := generatePassword(16, true, true, true, true, true) 	if err != nil { panic(err) } 	fmt.Println(p) } 

Additional features

  • Pronounceable passwords: use Markov chains or alternate consonant/vowel patterns to create easier‑to‑remember strings.
  • Passphrases: generate passphrases by randomly selecting words from a large wordlist (e.g., EFF wordlist) — often easier to memorize and can reach high entropy with fewer items.
  • Password policy export: save allowed character sets, min/max length, and history rules as JSON for reproducible generation.
  • UI: simple web UI with copy button and a strength meter; ensure generated passwords are not logged.

Testing and validation

  • Verify that when “require_each_class” is enabled, at least one char from each requested category appears.
  • Test entropy estimates against formula entropy = L * log2(N).
  • Fuzz test for boundary lengths, empty charset, and user toggles.

Security checklist before using

  • Use only CSPRNG features of your language/runtime.
  • Avoid printing passwords to logs or persistent storage.
  • Prefer passphrases for human memorization or a reputable password manager for cross‑device sync.
  • Review any third‑party libraries used for vulnerabilities.

Build, test, and iterate. The example code above is a solid starting point whether you want a terminal tool, a library, or a browser UI.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *