aboutsummaryrefslogtreecommitdiffstats
path: root/src/python/makepass.py
diff options
context:
space:
mode:
authorDennis Eriksen <d@ennis.no>2023-11-10 09:55:41 +0100
committerDennis Eriksen <d@ennis.no>2023-11-10 09:55:41 +0100
commit0f791d539e5d870919abb4ad2357c04c2c6b5c41 (patch)
treed6d8242400fc9d459ac70f38c314c4afa17889ab /src/python/makepass.py
parenttrying out new project structure (diff)
downloadmakepass-0f791d539e5d870919abb4ad2357c04c2c6b5c41.tar.gz
moving python-file to src
so I can add ruff-config and venv without cluttering the main folder
Diffstat (limited to 'src/python/makepass.py')
-rwxr-xr-xsrc/python/makepass.py318
1 files changed, 318 insertions, 0 deletions
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()