Go back

Generating Good Passwords

Published:

Generating Good Passwords

Table of Contents

Open Table of Contents

Introduction

Why Strong Passwords Matter

Whether you like them or not, passwords remain the primary line of defense for most online services. Weak or reused passwords are a leading cause of breaches—94% of exposed passwords in recent analyses were reused, and 42% were under 10 characters long (New York Post). Attackers use brute-force and dictionary attacks that become trivial when password entropy is low.

Even “random” passwords generated poorly can accidentally form dictionary words or offensive terms, harming trust and user experience (Request Tracker Community Forum).

The Human Factor

Despite advances in single-sign-on and 2FA, users still create and manage passwords. They write them down or reuse them across accounts: 27% of leaked credentials were simple lowercase+digits sequences like “password123” (New York Post). Automating password creation correctly minimizes human error while maximizing security.

Architecting a Secure Password Generator

Using C++23 and Thread-Local RNG

For high performance in multi-threaded environments, you want each thread to reuse its own RNG instance rather than re-seeding on every call. A thread-local std::mt19937_64 seeded once per thread offers both speed, strong statistical properties, and is thread-safe. For example:

#include <random>
#include <mutex>

// Thread-local RNG: seeded once per thread for minimal overhead
thread_local std::mt19937_64 rng{ std::random_device{}() };

This approach avoids costly random_device calls on each generation and is safe in C++23, where thread_local is well-supported (Stack Overflow, Stack Overflow). You should use std::uniform_int_distribution to map RNG output to your character set indices (Microsoft Learn).

Performance and Security Considerations

Crafting the Ideal Character Set

Character Categories

A robust password includes four types of characters:

  1. Uppercase letters (A–Z)
  2. Lowercase letters (a–z)
  3. Digits (0–9)
  4. Symbols (e.g., !@#$%^&*()-_+=)

Enforcing at least three of these four categories is a common practice (Western Michigan University).

Avoiding Ambiguous Characters

Characters like O vs 0, I vs l, and S vs 5 cause confusion when users type or transcribe passwords (Bitwarden Community Forums, Reddit). A typical “no-ambiguous” list excludes:

Letters removedDigits removed
O, I, l0, 1

This reduces user frustration while maintaining a large enough set for strong entropy.

International Keyboard-Friendly Symbols

Not all symbols are equally easy to type on non-US layouts. For broad compatibility, prefer punctuation available without complex dead keys:

! @ # $ % & * - _ + =

According to keyboard shortcut guides, these are reachable via standard keys on Windows (US-International) and macOS without invoking special input modes (SLCR).

Minimizing Offensive Substrings

Why CVC Patterns Lead to Problems

Random sequences can inadvertently form common swear words following a consonant–vowel–consonant (CVC) pattern (e.g., “fut”, “sot”) (Request Tracker Community Forum). Since most English swear words follow CVC roots, rejecting any generated substring of length 3 matching [C][V][C] cuts down risk substantially.

Excluding High-Risk Letters and Vowels

One approach is to omit all vowels (A, E, I, O, U) or at least the most problematic ones, alongside consonants frequently found in profanity (F, C, S, B, D, K) (Request Tracker Community Forum, Information Security Stack Exchange). This further shrinks the chance of accidental obscenities without requiring a full dictionary.

Note: Omitting vowels can reduce memorability and entropy; balance is key.

Swear-Word Blacklists: When to Use—and When to Avoid

Maintaining a swear-word blacklist can catch direct matches, but:

For most systems, rejecting CVC patterns combined with selective character omissions provides a simpler, performant compromise.

Common Password Blacklists: Not Needed

Some systems maintain lists of commonly-used passwords and reject any matches. With system-generated passwords, there is no need for such a list.

Additional Best Practices

Length and Entropy Recommendations

Entropy Calculation

Entropy EE for a uniform set size RR and length LL is:

E=L×log2RE = L \times \log_2 R

For example, with R=74R=74 and L=10L=10, E10×log2(74)64E \approx 10 \times \log_2(74) \approx 64 bits (NordVPN).

Password Reuse and Management

Even the strongest generator can’t prevent users from writing passwords on sticky notes or reusing them. Encourage:

C++23 Implementation Example

#include <random>
#include <string>
#include <vector>

// Define your character set:
static const std::string upper = "ABCDEFGHJKLMNPQRSTUVWXY"; // ex. omit I, Z if desired
static const std::string lower = "abcdefghijkmnpqrstuvwxyz"; // omit l,o
static const std::string digits = "23456789";                // omit 0,1
static const std::string symbols = "!@#$%&*-_+=";            // international-friendly

std::string generate_password(
    size_t length = 10,
    const std::string& charset = upper + lower + digits + symbols)
{
    thread_local std::mt19937_64 rng{ std::random_device{}() };
    std::uniform_int_distribution<size_t> dist(0, charset.size() - 1);

    std::string pw;
    pw.reserve(length);

    for (size_t i = 0; i < length; ++i) {
        char c;
        // Optional: check CVC pattern here and retry if necessary
        do {
            c = charset[dist(rng)];
        } while (/* check CVC or other rules */ false);

        pw += c;
    }
    return pw;
}

Estimating the Password Space

Even with strict omissions, the space of possible 10-character passwords remains astronomically large. Using a restricted 57-character set (after removing ambiguous letters, digits, high-risk consonants, and limited symbols):

LengthFull Set (74)ⁿRestricted Set (57)ⁿ
88.99 × 10¹⁴1.11 × 10¹¹
104.92 × 10¹⁸3.62 × 10¹⁷
122.70 × 10²²1.18 × 10²¹

Calculations per combination formula RLR^L (Information Security Stack Exchange, Omni Calculator).

Even the restricted set yields over 3.6 × 10¹⁷ possibilities for 10-character passwords—far beyond brute-force with modern hardware. Techniques such as salting the password and artificial delays in processing, both beyond the scope of this article, can further enhance its security.

Personal Anecdotes

Conclusion

A high-quality password generator balances performance, randomness, and practical usability. By leveraging C++23’s thread-local PRNGs, carefully choosing your character set, rejecting risky patterns, and following NIST/OWASP guidelines, you can produce passwords that are both secure and user-friendly. Even under tight restrictions, the password space remains vast, ensuring strong protection against brute-force and guessing attacks.


Suggest Changes

Previous Post
Robust Streaming Statistics: When Every Byte Counts
Next Post
Mastering C++23: The Future of Performance-Critical Programming