🎯 Stop Using Weak Passwords — Military-Grade Generator, One Script

:locked_with_key: Military-Grade Password Generator — Free, Open Source, Runs Offline

Your browser’s “suggested password” is a toy. This is the real thing.

This generator stacks four layers of cryptographic hardening before a single character hits your screen.

Think of it like a vault door with four independent locks — breaking one still leaves three standing. It uses the same math that protects classified government communications, except it runs on your laptop with a simple GUI. No internet. No cloud. No trust required. Copy the script, run it, and you own passwords that would take longer to crack than the sun has left to burn.


🧠 What Makes This Different — The 4-Layer Pipeline (Plain English)

Most password generators just grab random characters and call it a day. This one runs your randomness through four separate stages before you ever see a password. Here’s what each one does — no crypto degree needed.

Layer 1 — CSPRNG (the randomness source)
Think of it as rolling a perfectly fair die with billions of sides. Python’s secrets module pulls randomness from your operating system’s most secure source — the same one used for encryption keys. Not random.random() — that’s predictable. This isn’t.

Layer 2 — BB84 Quantum Mix (optional)
A software simulation of how quantum physicists share secret keys. Two virtual “people” pick random measurement settings — only the bits where they accidentally agree survive. It’s like shaking a bag of puzzle pieces and keeping only the ones that click together by chance. This doesn’t give you actual quantum security, but it adds another independent layer of chaos to the mix.

:light_bulb: Trick: Leave BB84 off for daily passwords — it adds computation time without meaningful benefit for most use cases. Turn it on when generating master passwords or encryption keys where you want maximum entropy diversification.

Layer 3 — HKDF-SHA512 (the entropy refinery)
Think of it as a coffee grinder for randomness. Raw random bits go in, uniformly distributed key material comes out. HKDF takes your raw entropy, adds a pepper (random secret seasoning), a context label, and extra random bytes — then processes everything through HMAC-SHA512. The output is mathematically proven to be indistinguishable from perfectly random data.

Layer 4 — scrypt Hardening (optional)
This is the “make it expensive” layer. Even if someone gets your derived key material, scrypt forces them to use massive amounts of memory to try each guess. It’s the same technology behind cryptocurrency mining difficulty. One password generation takes a fraction of a second for you — but trying billions of guesses would require a warehouse of RAM.

:light_bulb: Trick: Keep scrypt ON for anything you’ll type by hand (master passwords, encryption passphrases). The extra half-second of generation time buys you orders of magnitude more resistance against brute-force attacks.

Rejection Sampling — Zero Bias
One more thing most generators get wrong: when you pick a random number and use % (modulo) to fit it into your alphabet size, some characters become slightly more likely than others. This generator throws away any random byte that would cause bias and draws again. Every character in your password has exactly equal probability.

📊 Entropy Reference — How Strong Is Strong Enough?

Entropy measures how many guesses an attacker needs. Each “bit” doubles the number of guesses. Here’s where the industry draws its lines:

Entropy Status Where It’s Used What It Means
64 bits Dead Legacy junk A modern GPU cracks this over lunch
80 bits Deprecated Old banking standards Not enough anymore
112 bits Minimum NIST baseline Barely acceptable today
128 bits Modern standard AES-128, TLS, HTTPS Very strong — this is what protects your bank
192 bits High security AES-192 Would take all computers on Earth longer than the universe has existed
256 bits Government/military AES-256, classified systems Effectively unbreakable by any known physics
256+ bits Future-proof Critical infrastructure Survives even quantum computers

:light_bulb: Trick: At default settings (32 characters, all character classes ON), this generator produces ~190+ bits of entropy. That’s already beyond what most government systems require. Bump to 48+ characters if you want quantum-proof margins.

🖥️ The Generator — Full Source Code

Save this as password_gen.py and run it. Python 3.10+ required. No external dependencies — everything uses Python’s standard library.

#!/usr/bin/env python3
"""Auditable high-security password generator with bilingual GUI (RU/EN).

Security principles:
- CSPRNG-only randomness via `secrets`
- HKDF-SHA512 for entropy expansion
- optional memory-hard hardening with scrypt
- rejection sampling (no modulo bias)
- strict policy validation and bounded parameters
- transparent security messaging (no absolute-security claims)
"""

from __future__ import annotations

import hashlib
import hmac
import math
import secrets
import string
import threading
import tkinter as tk
from dataclasses import dataclass
from hashlib import sha512
from tkinter import messagebox, ttk

ALLOWED_SYMBOLS = "!@#$%^&*.()"
MIN_PASSWORD_LENGTH = 12
MAX_PASSWORD_LENGTH = 512
DEFAULT_SCRYPT_N = 2**14


def _unique_alphabet(*parts: str) -> str:
    seen: set[str] = set()
    result: list[str] = []
    for part in parts:
        for ch in part:
            if ch not in seen:
                seen.add(ch)
                result.append(ch)
    return "".join(result)


DEFAULT_ALPHABET = _unique_alphabet(string.ascii_letters, string.digits, ALLOWED_SYMBOLS)


@dataclass(frozen=True)
class PasswordPolicy:
    length: int = 32
    require_lower: bool = True
    require_upper: bool = True
    require_digit: bool = True
    require_symbol: bool = True

    def required_sets(self) -> list[str]:
        sets: list[str] = []
        if self.require_lower:
            sets.append(string.ascii_lowercase)
        if self.require_upper:
            sets.append(string.ascii_uppercase)
        if self.require_digit:
            sets.append(string.digits)
        if self.require_symbol:
            sets.append(ALLOWED_SYMBOLS)
        return sets

    def validate(self, alphabet: str) -> None:
        if not isinstance(self.length, int):
            raise ValueError("Password length must be an integer")
        if self.length < MIN_PASSWORD_LENGTH:
            raise ValueError(f"Password length must be at least {MIN_PASSWORD_LENGTH}")
        if self.length > MAX_PASSWORD_LENGTH:
            raise ValueError(f"Password length must be <= {MAX_PASSWORD_LENGTH}")
        if not alphabet:
            raise ValueError("Alphabet must not be empty")

        required = self.required_sets()
        if not required:
            raise ValueError("Enable at least one character class")
        if self.length < len(required):
            raise ValueError("Password length is too small for required classes")


def _random_bit_list(length: int) -> list[int]:
    if length <= 0:
        raise ValueError("Length must be positive")
    value = secrets.randbits(length)
    return [(value >> i) & 1 for i in range(length)]


def simulate_bb84_key(length: int = 1024) -> str:
    """BB84-style software sifting simulation.

    This is entropy diversification only, not physical QKD.
    """
    if length <= 0:
        raise ValueError("Length must be positive")

    out: list[str] = []
    while len(out) < length:
        batch = min(1024, length - len(out))
        sender_bits = _random_bit_list(batch)
        sender_basis = _random_bit_list(batch)
        receiver_basis = _random_bit_list(batch)
        for idx in range(batch):
            if sender_basis[idx] == receiver_basis[idx]:
                out.append(str(sender_bits[idx]))
    return "".join(out[:length])


def generate_csprng_bits(length: int = 1024) -> str:
    if length <= 0:
        raise ValueError("Length must be positive")
    return format(secrets.randbits(length), f"0{length}b")


def _bits_to_bytes(bits: str) -> bytes:
    if not bits:
        raise ValueError("Bit string must not be empty")
    padded_len = math.ceil(len(bits) / 8) * 8
    integer = int(bits.ljust(padded_len, "0"), 2)
    return integer.to_bytes(padded_len // 8, "big")


def _hkdf_extract(salt: bytes, ikm: bytes) -> bytes:
    return hmac.new(salt, ikm, sha512).digest()


def _hkdf_expand(prk: bytes, info: bytes, out_len: int) -> bytes:
    if out_len <= 0:
        raise ValueError("HKDF output length must be positive")
    if out_len > 255 * sha512().digest_size:
        raise ValueError("HKDF output length exceeds RFC 5869 limits")

    out = bytearray()
    prev = b""
    counter = 1
    while len(out) < out_len:
        prev = hmac.new(prk, prev + info + bytes([counter]), sha512).digest()
        out.extend(prev)
        counter += 1
    return bytes(out[:out_len])


def derive_entropy(master_bits: str, context: bytes, out_len: int, pepper: bytes) -> bytes:
    if not context:
        raise ValueError("Context is required")
    if not pepper:
        raise ValueError("Pepper is required")

    extra_entropy = secrets.token_bytes(64)
    ikm = _bits_to_bytes(master_bits) + extra_entropy + pepper
    salt = hashlib.sha256(context + pepper + b"/leafreader-hkdf-salt-v3").digest()
    prk = _hkdf_extract(salt, ikm)
    return _hkdf_expand(prk, b"leafreader/passwords/hkdf-v3/" + context, out_len)


def _rejection_sample(alphabet: str, count: int, seed: bytes) -> list[str]:
    if not alphabet:
        raise ValueError("Alphabet must not be empty")
    if count <= 0:
        raise ValueError("Count must be positive")

    out: list[str] = []
    limit = 256 - (256 % len(alphabet))
    pool = seed
    idx = 0

    while len(out) < count:
        if idx >= len(pool):
            pool = hashlib.sha512(pool + secrets.token_bytes(32)).digest()
            idx = 0
        val = pool[idx]
        idx += 1
        if val < limit:
            out.append(alphabet[val % len(alphabet)])

    return out


def _estimate_entropy_bits(alphabet: str, length: int, required_sets: list[str]) -> float:
    if not required_sets:
        return length * math.log2(len(alphabet))

    remaining = length - len(required_sets)
    if remaining < 0:
        raise ValueError("Length too small for required classes")

    combinations = math.comb(length, len(required_sets))
    mandatory_space = math.prod(len(s) for s in required_sets)
    free_space = len(alphabet) ** remaining
    return math.log2(combinations * mandatory_space * free_space)


def _strength_label(entropy_bits: float, lang: str) -> str:
    if entropy_bits < 70:
        return "Низкая" if lang == "ru" else "Low"
    if entropy_bits < 100:
        return "Средняя" if lang == "ru" else "Medium"
    if entropy_bits < 140:
        return "Высокая" if lang == "ru" else "High"
    return "Очень высокая" if lang == "ru" else "Very high"


def generate_strong_password(
    policy: PasswordPolicy,
    alphabet: str = DEFAULT_ALPHABET,
    use_bb84_mix: bool = False,
    use_scrypt: bool = True,
    scrypt_n: int = DEFAULT_SCRYPT_N,
) -> tuple[str, float]:
    policy.validate(alphabet)
    required = policy.required_sets()

    bits = generate_csprng_bits(1024)
    if use_bb84_mix:
        bb84_bits = simulate_bb84_key(1024)
        mixed = int(bits, 2) ^ int(bb84_bits, 2)
        bits = format(mixed, "01024b")

    pepper = secrets.token_bytes(32)
    context = secrets.token_bytes(16)
    key = derive_entropy(bits, context=context, out_len=128, pepper=pepper)

    if use_scrypt:
        if scrypt_n < 2 or scrypt_n & (scrypt_n - 1):
            raise ValueError("scrypt_n must be a power of two >= 2")
        key = hashlib.scrypt(
            password=key,
            salt=hashlib.sha256(context + pepper + b"/scrypt").digest(),
            n=scrypt_n,
            r=8,
            p=1,
            dklen=128,
        )

    password_chars = _rejection_sample(alphabet, policy.length, key)
    for idx, charset in enumerate(required):
        password_chars[idx] = _rejection_sample(charset, 1, secrets.token_bytes(32))[0]
    secrets.SystemRandom().shuffle(password_chars)

    entropy = _estimate_entropy_bits(alphabet, policy.length, required)
    return "".join(password_chars), entropy


I18N = {
    "ru": {
        "title": "Генератор безопасных паролей",
        "length": "Длина",
        "lower": "Строчные",
        "upper": "Заглавные",
        "digit": "Цифры",
        "symbol": "Символы",
        "bb84": "BB84-микс (экспериментально)",
        "scrypt": "Scrypt-усиление",
        "generate": "Сгенерировать",
        "copy": "Копировать",
        "lang": "Switch to English",
        "entropy": "Энтропия",
        "strength": "Стойкость",
        "copied": "Пароль скопирован в буфер обмена",
        "busy": "Генерация...",
        "ready": "Готово",
        "error": "Ошибка",
        "note": "Важно: абсолютной неуязвимости не бывает; стойкость зависит от длины, политики и защиты хранилища.",
    },
    "en": {
        "title": "Secure Password Generator",
        "length": "Length",
        "lower": "Lowercase",
        "upper": "Uppercase",
        "digit": "Digits",
        "symbol": "Symbols",
        "bb84": "BB84 mix (experimental)",
        "scrypt": "Scrypt hardening",
        "generate": "Generate",
        "copy": "Copy",
        "lang": "Переключить на русский",
        "entropy": "Entropy",
        "strength": "Strength",
        "copied": "Password copied to clipboard",
        "busy": "Generating...",
        "ready": "Ready",
        "error": "Error",
        "note": "Important: no absolute immunity exists; strength depends on length, policy, and storage-side defenses.",
    },
}


class PasswordGUI:
    def __init__(self, root: tk.Tk) -> None:
        self.root = root
        self.lang = "ru"
        self.root.geometry("740x430")
        self.root.minsize(700, 420)

        self.length_var = tk.IntVar(value=32)
        self.lower_var = tk.BooleanVar(value=True)
        self.upper_var = tk.BooleanVar(value=True)
        self.digit_var = tk.BooleanVar(value=True)
        self.symbol_var = tk.BooleanVar(value=True)
        self.bb84_var = tk.BooleanVar(value=False)
        self.scrypt_var = tk.BooleanVar(value=True)

        self.password_var = tk.StringVar()
        self.entropy_var = tk.StringVar()
        self.strength_var = tk.StringVar()
        self.status_var = tk.StringVar()

        self._build_ui()
        self._apply_i18n()

    def _build_ui(self) -> None:
        frame = ttk.Frame(self.root, padding=14)
        frame.pack(fill=tk.BOTH, expand=True)

        self.title_label = ttk.Label(frame, font=("Arial", 15, "bold"))
        self.title_label.pack(anchor=tk.W, pady=(0, 10))

        row = ttk.Frame(frame)
        row.pack(fill=tk.X)
        self.length_label = ttk.Label(row)
        self.length_label.pack(side=tk.LEFT)
        ttk.Spinbox(row, from_=MIN_PASSWORD_LENGTH, to=MAX_PASSWORD_LENGTH, textvariable=self.length_var, width=8).pack(side=tk.LEFT, padx=8)

        checks = ttk.Frame(frame)
        checks.pack(fill=tk.X, pady=8)
        self.cb_lower = ttk.Checkbutton(checks, variable=self.lower_var)
        self.cb_upper = ttk.Checkbutton(checks, variable=self.upper_var)
        self.cb_digit = ttk.Checkbutton(checks, variable=self.digit_var)
        self.cb_symbol = ttk.Checkbutton(checks, variable=self.symbol_var)
        self.cb_bb84 = ttk.Checkbutton(checks, variable=self.bb84_var)
        self.cb_scrypt = ttk.Checkbutton(checks, variable=self.scrypt_var)

        self.cb_lower.grid(row=0, column=0, sticky=tk.W, padx=(0, 12))
        self.cb_upper.grid(row=0, column=1, sticky=tk.W, padx=(0, 12))
        self.cb_digit.grid(row=0, column=2, sticky=tk.W, padx=(0, 12))
        self.cb_symbol.grid(row=0, column=3, sticky=tk.W)
        self.cb_bb84.grid(row=1, column=0, sticky=tk.W, pady=(4, 0), padx=(0, 12))
        self.cb_scrypt.grid(row=1, column=1, sticky=tk.W, pady=(4, 0))

        controls = ttk.Frame(frame)
        controls.pack(fill=tk.X, pady=(8, 8))
        self.generate_btn = ttk.Button(controls, command=self.generate_async)
        self.generate_btn.pack(side=tk.LEFT)
        self.copy_btn = ttk.Button(controls, command=self.copy_password)
        self.copy_btn.pack(side=tk.LEFT, padx=8)
        self.lang_btn = ttk.Button(controls, command=self.toggle_language)
        self.lang_btn.pack(side=tk.RIGHT)

        ttk.Entry(frame, textvariable=self.password_var, font=("Consolas", 12)).pack(fill=tk.X, pady=(0, 8))
        ttk.Label(frame, textvariable=self.entropy_var).pack(anchor=tk.W)
        ttk.Label(frame, textvariable=self.strength_var).pack(anchor=tk.W)

        self.note_label = ttk.Label(frame, foreground="#555555", wraplength=700)
        self.note_label.pack(anchor=tk.W, pady=(8, 4))

        ttk.Separator(frame).pack(fill=tk.X, pady=(4, 4))
        ttk.Label(frame, textvariable=self.status_var).pack(anchor=tk.W)

    def _apply_i18n(self) -> None:
        t = I18N[self.lang]
        self.root.title(t["title"])
        self.title_label.configure(text=t["title"])
        self.length_label.configure(text=t["length"] + ":")
        self.cb_lower.configure(text=t["lower"])
        self.cb_upper.configure(text=t["upper"])
        self.cb_digit.configure(text=t["digit"])
        self.cb_symbol.configure(text=t["symbol"])
        self.cb_bb84.configure(text=t["bb84"])
        self.cb_scrypt.configure(text=t["scrypt"])
        self.generate_btn.configure(text=t["generate"])
        self.copy_btn.configure(text=t["copy"])
        self.lang_btn.configure(text=t["lang"])
        self.note_label.configure(text=t["note"])
        if not self.status_var.get():
            self.status_var.set(t["ready"])

    def toggle_language(self) -> None:
        self.lang = "en" if self.lang == "ru" else "ru"
        self._apply_i18n()

    def generate_async(self) -> None:
        self.generate_btn.configure(state=tk.DISABLED)
        self.status_var.set(I18N[self.lang]["busy"])
        threading.Thread(target=self._worker, daemon=True).start()

    def _worker(self) -> None:
        try:
            policy = PasswordPolicy(
                length=int(self.length_var.get()),
                require_lower=self.lower_var.get(),
                require_upper=self.upper_var.get(),
                require_digit=self.digit_var.get(),
                require_symbol=self.symbol_var.get(),
            )
            password, entropy = generate_strong_password(
                policy=policy,
                use_bb84_mix=self.bb84_var.get(),
                use_scrypt=self.scrypt_var.get(),
            )
            self.root.after(0, self._render_result, password, entropy)
        except Exception as exc:
            self.root.after(0, self._render_error, str(exc))

    def _render_result(self, password: str, entropy: float) -> None:
        t = I18N[self.lang]
        self.password_var.set(password)
        self.entropy_var.set(f"{t['entropy']}: ~{entropy:.1f} bits")
        self.strength_var.set(f"{t['strength']}: {_strength_label(entropy, self.lang)}")
        self.status_var.set(t["ready"])
        self.generate_btn.configure(state=tk.NORMAL)

    def _render_error(self, message: str) -> None:
        self.generate_btn.configure(state=tk.NORMAL)
        self.status_var.set(I18N[self.lang]["ready"])
        messagebox.showerror(I18N[self.lang]["error"], message)

    def copy_password(self) -> None:
        password = self.password_var.get()
        if not password:
            return
        self.root.clipboard_clear()
        self.root.clipboard_append(password)
        messagebox.showinfo("OK", I18N[self.lang]["copied"])


def main() -> None:
    root = tk.Tk()
    PasswordGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()
🖼️ GUI Preview

Bilingual interface — switches between Russian and English with one click. All settings right there: password length slider, character class toggles, BB84 and scrypt switches. Hit Generate, copy, done.

⚙️ How to Run — Step by Step

Step 1 — Make sure you have Python 3.10 or newer installed. Open a terminal (Command Prompt on Windows, Terminal on Mac/Linux) and type:

This checks your Python version:

python --version

If it says 3.10 or higher, you’re good. If not, grab Python from python.org.

Step 2 — Copy the entire code block from the section above. Save it as a file called password_gen.py anywhere on your computer.

Step 3 — Open your terminal, navigate to where you saved the file, and run:

This launches the password generator window:

python password_gen.py

Step 4 — The GUI pops up. Set your desired password length (default is 32 — already very strong), toggle character classes, flip scrypt ON, and hit Generate.

Step 5 — Click Copy. Paste it wherever you need it.

:light_bulb: Trick: No internet connection needed at any point. This runs entirely offline. That’s the whole point — your passwords never touch a network, a server, or anyone else’s code.

🛡️ The Quantum Angle — Why 256+ Bits Matters

Quantum computers are coming. Here’s what that actually means for your passwords — no hype, no panic.

Symmetric crypto (passwords, AES, hashes) — you lose half your bits.
A quantum algorithm called Grover’s gives attackers a square-root speedup on brute force. In practice: 128-bit security drops to effectively 64-bit. 256-bit drops to 128-bit. That 128-bit effective security is still beyond anything attackable — but it means the margin matters.

Asymmetric crypto (RSA, ECC) — completely destroyed.
A different quantum algorithm called Shor’s can factor large numbers efficiently. RSA and elliptic curve crypto lose nearly 100% of their security. This doesn’t affect password generators directly, but it’s why NIST is rushing to standardize post-quantum algorithms by ~2030.

The bottom line:
Passwords with 256+ bits of entropy survive the quantum era just fine. The length slider in this generator goes up to 512 characters. That is precisely why I added the ability to adjust the password length, thereby directly controlling the entropy level. When these standards evolve in the future, you will be able to easily adapt and recalibrate the security accordingly.

✅ Do's and Don'ts
Do Don’t
Use 32+ character length — default is already solid Don’t use 12-character minimums for anything important — that’s the floor, not the goal
Keep scrypt ON for master passwords and encryption keys Don’t disable all character classes — the generator will reject it, but don’t try
Run it offline — that’s the entire security model Don’t paste the generated password into “password strength checker” websites — you just leaked it
Save the script locally — it’s yours forever, no dependencies Don’t modify the crypto functions unless you know exactly what you’re doing
Use different passwords per service — generate a fresh one each time Don’t reuse passwords across services — one breach exposes everything
Store passwords in a proper manager (KeePass, Bitwarden) Don’t save them in a text file on your desktop — that’s a password graveyard

:high_voltage: Quick Hits

Want Do
:locked_with_key: Strong daily password → Run script, 32 chars, scrypt ON, copy
:shield: Master password → 48+ chars, scrypt ON, BB84 ON
:key: Encryption key → Max length, all layers ON
:globe_with_meridians: Quantum-proof margin → 64+ chars = 380+ bits entropy
:russia: Russian interface → Click the language toggle button

Your passwords are only as strong as the weakest system that stores them — but at least the generation side is no longer the weak link.

7 Likes