diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | README.md | 12 | ||||
l---------[-rwxr-xr-x] | makepass.py | 319 | ||||
-rwxr-xr-x | src/python/makepass.py | 318 | ||||
-rw-r--r-- | src/python/ruff.toml | 64 |
5 files changed, 398 insertions, 318 deletions
@@ -2,3 +2,6 @@ src/rust/target src/rust/debug src/go/makepass.go + +# virtualenvs +src/python/venv @@ -218,6 +218,18 @@ Perl version. I like this version <3 ### Python I don't really care for python, but here it is. It's pretty slow, but I don't really know Python. It should probably be faster. +To lint and format with `ruff`, `ruff` must first be installed. + +``` +$ cd src/python +$ python3 -m venv venv +$ source venv/bin/activate +$ pip install --upgrade pip +$ pip install ruff +$ ruff check makepass.py +$ ruff format makepass.py +``` + ### Rust Rust-version. It's fast! diff --git a/makepass.py b/makepass.py index 233ae7d..b6e1064 100755..120000 --- a/makepass.py +++ b/makepass.py @@ -1,318 +1 @@ -#!/usr/bin/env python3 -# -# Author : Dennis Eriksen <d@ennis.no> -# File : makepass.py -# Created : 2023-09-14 -# License : BSD-3-Clause -# -# Copyright (c) 2018-2023 Dennis Eriksen <d@ennis.no> - -import argparse -import os -import random -import sys - -# Global variables - -MAX = 255 -RANGE_MAX = 42 -RANGE_MIN = 8 -PASS_WORDS = 8 - -LOWER = "abcdefghijklmnopqrstuvwxyz" -UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -DIGIT = "0123456789" -OTHER = "!$%&#/()=?+-_,.;:<>[]{}|@*" - -ALPHA = LOWER + UPPER -ALNUM = ALPHA + DIGIT -EVERY = ALNUM + OTHER - -NORMAL = ALNUM + "_-" -SPECIAL = EVERY - -DEFAULT_LENGTH = 0 -DEFAULT_NUMBER = 10 -DEFAULT_WORDLIST = "/usr/share/dict/words" - - -class CommonPrintData: - def __init__(self, length, printbool, printlen, col_width, col_num): - self.length = length - self.printbool = printbool - self.printlen = printlen - self.col_num = col_num - self.col_width = col_width - - -# -# Main function -# -def main(): - normal = os.getenv("MAKEPASS_NORMAL") or NORMAL - special = os.getenv("MAKEPASS_SPECIAL") or SPECIAL - wordlist_file = os.getenv("MAKEPASS_WORDLIST") or DEFAULT_WORDLIST - - # Initialize parser - parser = argparse.ArgumentParser( - description="Process some strings.", add_help=False - ) - - # Add arguments - parser.add_argument("-h", "--help", action="store_true") - parser.add_argument("-l", "--length", type=int, help="Length of strings") - parser.add_argument("-n", "--number", type=int, help="Number of strings") - parser.add_argument("-p", "--printlen", action="store_true") - parser.add_argument("arg_length", type=int, nargs="?") - - # Parse arguments - args = parser.parse_args() - - if args.help: - usage() - - # Some error-checking - # length - try: - # env or default - length = int(os.getenv("MAKEPASS_LENGTH") or DEFAULT_LENGTH) - - # -l flag - if args.length is not None: - if args.length < 0 or MAX < args.length: - raise ValueError("-l") - length = args.length - - # solo argument - if args.arg_length is not None: - length = args.arg_length - - # check result - if length < 0 or MAX < length: - raise ValueError("length") - except ValueError as e: - print(f"{e} must be a number between 0 and {MAX}") - sys.exit(1) - - # number - try: - # env or default - number = int(os.getenv("MAKEPASS_NUMBER") or DEFAULT_NUMBER) - - # -n flag - if args.number is not None: - if args.number < 1 or MAX < args.number: - raise ValueError("-n") - number = args.number - - # check result - if number < 1 or MAX < number: - raise ValueError("number") - except ValueError as e: - print(f"{e} must be a number between 1 and {MAX}") - sys.exit(1) - - # Set some other stuff - - printbool = args.printlen - if printbool: - printlen = 3 if length < 100 else 4 - else: - printlen = 0 - - # get terminal width - try: - xwidth = os.get_terminal_size().columns - except Exception as _: - xwidth = 1 - - # get width of columns to print - if length == 0: - col_width = RANGE_MAX + 2 - else: - col_width = length + 2 - - # number of columns to print - col_num = int(xwidth / (col_width + printlen)) - if col_num == 0: - col_num = 1 - - # config - config = CommonPrintData( - length=length, - printbool=printbool, - printlen=printlen, - col_width=col_width, - col_num=col_num, - ) - - # - # print passwords - # - - # Generate and print normal and special passwords - print_columns("Normal passwords", number, normal, config) - - print() - - print_columns( - "Passwords with special characters", int(number / 3 * 2 + 1), special, config - ) - - # Generate and print passphrases if wordlist exists - if os.path.isfile(wordlist_file): - print() - - print("Generating passphrases: ") - passphrase(int(number / 2), wordlist_file) - - -# -# Print passwords in neat columns -# -def print_columns(title: str, num: int, chars: str, c: CommonPrintData): - # Generate random strings - strings = [randstring(c.length, chars) for _ in range(num)] - - print(f"{title}:") - - for i in range(num): - # Print the length of the string if printlen is set - if c.printbool: - print(f"{len(strings[i]):0{c.printlen-1}}", end=" ") - - # Print the string, left justified based on column width - print(f"{strings[i]:<{c.col_width}}", end="") - - # If we have printed enough strings for a single line or the - # rest of strings are fewer than column number, we break lines. - i += 1 - if i % c.col_num == 0 or (i == num and i % c.col_num > 0): - print() - - -# -# Generate random strings that can work as passwords -# -def randstring(length, chars): - """ - Generate a random password of the specified length. Use special - characters if special_chars option is True. - """ - if length == 0: - length = random.randint(RANGE_MIN, RANGE_MAX) - - password = [] - password.append(random.choice(ALPHA)) - for i in range(length - 2): - password.append(random.choice(chars)) - password.append(random.choice(ALNUM)) - - # Randomly reorder the characters - return "".join(password) - - -# -# Passphrases! -# -def passphrase(number, file_name): - """ - Generate a number of random passphrases from the wordlist. - """ - try: - with open(file_name, "r") as f: - lines = f.read().splitlines() - except IOError: - print(f"Cannot open file: {file_name}") - sys.exit(1) - - for i in range(number): - word_list = random.choices(lines, k=5) - passphrase = "-".join(word_list) - print(passphrase) - - -def usage(): - text = """NAME - makepass - create several random passwords - -SYNOPSIS - makepass [OPTIONS] [NUM] - - If a NUM is provided, passwords will be NUM characters long. - - By default `makepass` will output passwords from the three following classes: - - - Normal passwords - random strings with letters (both lower and upper - case), numbers, and dashes and underscores. - - - Passwords with special characters - random strings generated from lower - and upper case letters, numbers, and the following characters: - !#$%&/()=?+-_,.;:<>[]{}|@* - - - Passphrases - if we find a dictionary, a series of eight random words - from the dictionary, separated by dashes. The number of words can not be - changed, but you do not have to use all of them. Use as mane as you want. - - The first character will always be alphabetic, and the last will always be - alphanumeric. - -DESCRIPTION - makepass has the following options: - - -h - output this help-text - -l - length of passwords. See MAKEPASS_LENGTH below - -n - number of passwords. See MAKEPASS_NUMBER below - -p - print length of number - -ENVIRONMENT - makepass examines the following environmental variables. - - MAKEPASS_LENGTH - Specifies the length of passwords. Valid values are 0-255. If 0, a - random value between 8 and 42 will be used for each password. -l - overrides this environmental variable, and the argument NUM overrides - that again. So `MAKEPASS_LENGTH=10 makepass -l 12 14` will give - passwords that are 14 characters long, even though both -l and - MAKEPASS_LENGTH also specifies a length. - - MAKEPASS_NUMBER - The number of passwords to generate. This formula is used to determine - how many passwords from each group should be generated: - - (n) normal passwords - - (n / 3 * 2 + 1) special passwords - - (n / 2) passphrases - Where n is 10 by default. Valid values for n are 1-255. Floating-poing - math is not used, so results may vary. - - MAKEPASS_PRINTLEN - If 1, print length of all passwords. If 0, don\'t. - - MAKEPASS_NORMAL - String of characters from which to generate "normal" passwords. - Defaults to: - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ - - MAKEPASS_SPECIAL - String of characters from which to generate passwords with special - characters. Defaults to the same characters as in MAKEPASS_NORMAL, plus - these: - !#$%&/()=?+-_,.;:<>[]{}|@* - - MAKEPASS_WORDLIST - Specifies the dictionary we find words for passphrases in. If this is - unset or empty, we try "/usr/share/dict/words". If that file does not - exist, no passphrases will be provided. - -AUTHOR - Dennis Eriksen <https://dnns.no>""" - print(text) - sys.exit(0) - - -if __name__ == "__main__": - main() +src/python/makepass.py
\ No newline at end of file diff --git a/src/python/makepass.py b/src/python/makepass.py new file mode 100755 index 0000000..233ae7d --- /dev/null +++ b/src/python/makepass.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +# +# Author : Dennis Eriksen <d@ennis.no> +# File : makepass.py +# Created : 2023-09-14 +# License : BSD-3-Clause +# +# Copyright (c) 2018-2023 Dennis Eriksen <d@ennis.no> + +import argparse +import os +import random +import sys + +# Global variables + +MAX = 255 +RANGE_MAX = 42 +RANGE_MIN = 8 +PASS_WORDS = 8 + +LOWER = "abcdefghijklmnopqrstuvwxyz" +UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +DIGIT = "0123456789" +OTHER = "!$%&#/()=?+-_,.;:<>[]{}|@*" + +ALPHA = LOWER + UPPER +ALNUM = ALPHA + DIGIT +EVERY = ALNUM + OTHER + +NORMAL = ALNUM + "_-" +SPECIAL = EVERY + +DEFAULT_LENGTH = 0 +DEFAULT_NUMBER = 10 +DEFAULT_WORDLIST = "/usr/share/dict/words" + + +class CommonPrintData: + def __init__(self, length, printbool, printlen, col_width, col_num): + self.length = length + self.printbool = printbool + self.printlen = printlen + self.col_num = col_num + self.col_width = col_width + + +# +# Main function +# +def main(): + normal = os.getenv("MAKEPASS_NORMAL") or NORMAL + special = os.getenv("MAKEPASS_SPECIAL") or SPECIAL + wordlist_file = os.getenv("MAKEPASS_WORDLIST") or DEFAULT_WORDLIST + + # Initialize parser + parser = argparse.ArgumentParser( + description="Process some strings.", add_help=False + ) + + # Add arguments + parser.add_argument("-h", "--help", action="store_true") + parser.add_argument("-l", "--length", type=int, help="Length of strings") + parser.add_argument("-n", "--number", type=int, help="Number of strings") + parser.add_argument("-p", "--printlen", action="store_true") + parser.add_argument("arg_length", type=int, nargs="?") + + # Parse arguments + args = parser.parse_args() + + if args.help: + usage() + + # Some error-checking + # length + try: + # env or default + length = int(os.getenv("MAKEPASS_LENGTH") or DEFAULT_LENGTH) + + # -l flag + if args.length is not None: + if args.length < 0 or MAX < args.length: + raise ValueError("-l") + length = args.length + + # solo argument + if args.arg_length is not None: + length = args.arg_length + + # check result + if length < 0 or MAX < length: + raise ValueError("length") + except ValueError as e: + print(f"{e} must be a number between 0 and {MAX}") + sys.exit(1) + + # number + try: + # env or default + number = int(os.getenv("MAKEPASS_NUMBER") or DEFAULT_NUMBER) + + # -n flag + if args.number is not None: + if args.number < 1 or MAX < args.number: + raise ValueError("-n") + number = args.number + + # check result + if number < 1 or MAX < number: + raise ValueError("number") + except ValueError as e: + print(f"{e} must be a number between 1 and {MAX}") + sys.exit(1) + + # Set some other stuff + + printbool = args.printlen + if printbool: + printlen = 3 if length < 100 else 4 + else: + printlen = 0 + + # get terminal width + try: + xwidth = os.get_terminal_size().columns + except Exception as _: + xwidth = 1 + + # get width of columns to print + if length == 0: + col_width = RANGE_MAX + 2 + else: + col_width = length + 2 + + # number of columns to print + col_num = int(xwidth / (col_width + printlen)) + if col_num == 0: + col_num = 1 + + # config + config = CommonPrintData( + length=length, + printbool=printbool, + printlen=printlen, + col_width=col_width, + col_num=col_num, + ) + + # + # print passwords + # + + # Generate and print normal and special passwords + print_columns("Normal passwords", number, normal, config) + + print() + + print_columns( + "Passwords with special characters", int(number / 3 * 2 + 1), special, config + ) + + # Generate and print passphrases if wordlist exists + if os.path.isfile(wordlist_file): + print() + + print("Generating passphrases: ") + passphrase(int(number / 2), wordlist_file) + + +# +# Print passwords in neat columns +# +def print_columns(title: str, num: int, chars: str, c: CommonPrintData): + # Generate random strings + strings = [randstring(c.length, chars) for _ in range(num)] + + print(f"{title}:") + + for i in range(num): + # Print the length of the string if printlen is set + if c.printbool: + print(f"{len(strings[i]):0{c.printlen-1}}", end=" ") + + # Print the string, left justified based on column width + print(f"{strings[i]:<{c.col_width}}", end="") + + # If we have printed enough strings for a single line or the + # rest of strings are fewer than column number, we break lines. + i += 1 + if i % c.col_num == 0 or (i == num and i % c.col_num > 0): + print() + + +# +# Generate random strings that can work as passwords +# +def randstring(length, chars): + """ + Generate a random password of the specified length. Use special + characters if special_chars option is True. + """ + if length == 0: + length = random.randint(RANGE_MIN, RANGE_MAX) + + password = [] + password.append(random.choice(ALPHA)) + for i in range(length - 2): + password.append(random.choice(chars)) + password.append(random.choice(ALNUM)) + + # Randomly reorder the characters + return "".join(password) + + +# +# Passphrases! +# +def passphrase(number, file_name): + """ + Generate a number of random passphrases from the wordlist. + """ + try: + with open(file_name, "r") as f: + lines = f.read().splitlines() + except IOError: + print(f"Cannot open file: {file_name}") + sys.exit(1) + + for i in range(number): + word_list = random.choices(lines, k=5) + passphrase = "-".join(word_list) + print(passphrase) + + +def usage(): + text = """NAME + makepass - create several random passwords + +SYNOPSIS + makepass [OPTIONS] [NUM] + + If a NUM is provided, passwords will be NUM characters long. + + By default `makepass` will output passwords from the three following classes: + + - Normal passwords - random strings with letters (both lower and upper + case), numbers, and dashes and underscores. + + - Passwords with special characters - random strings generated from lower + and upper case letters, numbers, and the following characters: + !#$%&/()=?+-_,.;:<>[]{}|@* + + - Passphrases - if we find a dictionary, a series of eight random words + from the dictionary, separated by dashes. The number of words can not be + changed, but you do not have to use all of them. Use as mane as you want. + + The first character will always be alphabetic, and the last will always be + alphanumeric. + +DESCRIPTION + makepass has the following options: + + -h + output this help-text + -l + length of passwords. See MAKEPASS_LENGTH below + -n + number of passwords. See MAKEPASS_NUMBER below + -p + print length of number + +ENVIRONMENT + makepass examines the following environmental variables. + + MAKEPASS_LENGTH + Specifies the length of passwords. Valid values are 0-255. If 0, a + random value between 8 and 42 will be used for each password. -l + overrides this environmental variable, and the argument NUM overrides + that again. So `MAKEPASS_LENGTH=10 makepass -l 12 14` will give + passwords that are 14 characters long, even though both -l and + MAKEPASS_LENGTH also specifies a length. + + MAKEPASS_NUMBER + The number of passwords to generate. This formula is used to determine + how many passwords from each group should be generated: + - (n) normal passwords + - (n / 3 * 2 + 1) special passwords + - (n / 2) passphrases + Where n is 10 by default. Valid values for n are 1-255. Floating-poing + math is not used, so results may vary. + + MAKEPASS_PRINTLEN + If 1, print length of all passwords. If 0, don\'t. + + MAKEPASS_NORMAL + String of characters from which to generate "normal" passwords. + Defaults to: + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ + + MAKEPASS_SPECIAL + String of characters from which to generate passwords with special + characters. Defaults to the same characters as in MAKEPASS_NORMAL, plus + these: + !#$%&/()=?+-_,.;:<>[]{}|@* + + MAKEPASS_WORDLIST + Specifies the dictionary we find words for passphrases in. If this is + unset or empty, we try "/usr/share/dict/words". If that file does not + exist, no passphrases will be provided. + +AUTHOR + Dennis Eriksen <https://dnns.no>""" + print(text) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/src/python/ruff.toml b/src/python/ruff.toml new file mode 100644 index 0000000..1f0c2f8 --- /dev/null +++ b/src/python/ruff.toml @@ -0,0 +1,64 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.10 +target-version = "py310" + +[lint] +# https://docs.astral.sh/ruff/rules/ +# Enable: +# Pyflakes (`F`) +# Pycodestyle (`E`) +# Whitespace-warnings (`W`) +# isort (`I`) +select = ["E", "F", "W", "I"] + +# E501 too long lines +# W191 tab indents - `ruff format` recommends this be ignored +ignore = ["E501", "W191"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" |