Created
July 12, 2026 16:54
-
-
Save 0x9900/405d5099c6d44813757967cb823b2a06 to your computer and use it in GitHub Desktop.
Yet another password generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # vim:fenc=utf-8 | |
| # | |
| # Copyright © 2026 fred <github-fred@hidzz.com> | |
| # | |
| # Distributed under terms of the BSD 3-Clause license. | |
| import os | |
| import secrets | |
| import sys | |
| from functools import lru_cache | |
| from pathlib import Path | |
| from typing import List | |
| DICT_FILE = Path('/usr/share/dict/words') | |
| # Most common word with a length of 3 to 6 characters. | |
| # https://gist.github.com/deekayen/4148741 | |
| COMMON_WORDS = [ | |
| "that", "have", "with", "this", "from", "they", "will", "would", "there", | |
| "their", "what", "about", "which", "when", "make", "like", "time", "just", | |
| "know", "take", "people", "into", "year", "your", "good", "some", "could", | |
| "them", "other", "than", "then", "look", "only", "come", "over", "think", | |
| "also", "back", "after", "work", "first", "well", "even", "want", "these", | |
| "give", "most", | |
| ] | |
| # Secure random for shuffling | |
| _RANDOM = secrets.SystemRandom() | |
| @lru_cache(maxsize=1) | |
| def all_words() -> List[str]: | |
| words = set() | |
| if not DICT_FILE.exists(): | |
| print(f'The dictionnary file "{DICT_FILE}" does not exists') | |
| sys.exit(os.EX_OSFILE) | |
| with DICT_FILE.open('r', encoding='utf-8', errors='replace') as fd: | |
| for word in fd: | |
| word = word.strip().lower() | |
| if word in COMMON_WORDS: | |
| continue | |
| # Remove words with accents because they might be challenging to type | |
| # with some keyboards. | |
| if "'" in word or not word.isascii(): | |
| continue | |
| if 3 < len(word) < 6: | |
| words.add(word) | |
| print(f'=== {len(words)} selected ===') | |
| return list(words) | |
| def _maybe_transform(word: str) -> str: | |
| """Optionally transform a word with leetspeak substitutions.""" | |
| # Use a consistent mapping | |
| leet_map = {'i': '!', 'e': '3', 'o': '0', 'a': '@', 's': '$'} | |
| # Transform 40% of transformable characters | |
| transformed = [] | |
| chances = 50 | |
| for char in word: | |
| if char in leet_map and secrets.randbelow(100) < chances: | |
| transformed.append(leet_map[char]) | |
| chances = chances - 5 if chances > 0 else chances | |
| else: | |
| transformed.append(char) | |
| return ''.join(transformed) | |
| def make_password(length: int = 4, leet_chance: float = 0.4) -> str: | |
| if length < 1: | |
| raise ValueError('Length must be at least 1') | |
| words = all_words() | |
| if not words: | |
| raise RuntimeError('No words available for password generation') | |
| password = [secrets.choice(words) for _ in range(length)] | |
| # Apply leetspeak substitution to random words | |
| for i in range(length): | |
| if secrets.randbelow(100) < leet_chance * 100: | |
| password[i] = _maybe_transform(password[i]) | |
| _RANDOM.shuffle(password) | |
| return '-'.join(password) | |
| def main(): | |
| for _ in range(6): | |
| password = make_password(4) | |
| print(f'{len(password)} = {password}') | |
| if __name__ == "__main__": | |
| main() | |
| # |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment