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
-
Character sets
- Lowercase letters: a–z
- Uppercase letters: A–Z
- Digits: 0–9
- Symbols: !“#$%&‘()*+,-./:;<=>?@[]^_`{|}~
- Exclude ambiguous characters (e.g., l, I, 0, O) optionally.
-
Length
- Minimum recommended: 12 characters for general use.
- For highly sensitive accounts, prefer 16+.
-
Randomness source
- Use a cryptographically secure random number generator (CSPRNG).
- Avoid standard nonsecure RNGs (e.g., rand(), Math.random()) for security‑sensitive passwords.
-
Policies & constraints
- Allow toggles for character type inclusion.
- Optionally enforce at least one character from each selected category.
- Optionally block repeating characters or sequences.
-
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.
Leave a Reply