/home/alex/dev/passwd-gen.py (1)

From RaySoft
#!/usr/local/bin/python3
# ------------------------------------------------------------------------------
# passwd-gen.py
# =============
#
# Scope     Native
# Copyright (C) 2024 by RaySoft, Zurich, Switzerland
# License   GNU General Public License (GPL) 2.0
#           https://www.gnu.org/licenses/gpl2.txt
#
# ------------------------------------------------------------------------------

DEFAULT_LENGTH = 25
DEFAULT_AMOUNT = 10

# ------------------------------------------------------------------------------

PROGRAM_NAME = 'passwd-gen'
PROGRAM_VERSION = '0.2'

# ------------------------------------------------------------------------------

from argparse import ArgumentParser
from random import SystemRandom
from re import search, sub, IGNORECASE
from string import ascii_uppercase, ascii_lowercase, digits
from sys import exit, stderr

# ------------------------------------------------------------------------------

def main():
    parser = ArgumentParser(
        prog=PROGRAM_NAME, allow_abbrev=False,
        description='Generate random password',
        epilog='If the character set is not defined, all characters are used',
    )
    parser.add_argument(
        '-V', '--version', action='version',
        version=f'%(prog)s {PROGRAM_VERSION}',
    )
    parser.add_argument(
        '-a', '--amount', action='store',
        type=int, metavar='NUMBER', default=DEFAULT_AMOUNT,
        help=f'amount of the generated passwords '
             f'[default: {DEFAULT_AMOUNT:d}]',
    )
    parser.add_argument(
        '-l', '--length', action='store',
        type=int, metavar='NUMBER', default=DEFAULT_LENGTH,
        help=f'length in characters of the generated passwords '
             f'[default: {DEFAULT_LENGTH:d}]',
    )
    parser.add_argument(
        '-L', '--lower', action='store_true',
        help='use lowercase characters',
    )
    parser.add_argument(
        '-N', '--number', action='store_true',
        help='use numbers',
    )
    parser.add_argument(
        '-S', '--special', action='store_true',
        help='use special characters',
    )
    parser.add_argument(
        '-U', '--upper', action='store_true',
        help='use lowercase characters',
    )

    args = parser.parse_args()

    if args.length < 2:
        print('Password length must be at least 2 characters.', file=stderr)
        parser.print_usage(file=stderr)
        return 1

    if args.amount < 1:
        print('Password amount must be at least 1.', file=stderr)
        parser.print_usage(file=stderr)
        return 1

    charset = ''
    counter = 0

    types = {
        'upper':   [ascii_uppercase + 'ÄÖÜ', 1/4],
        'lower':   [ascii_lowercase + 'äöü', 1/4],
        'number':  [digits,                  1/8],
        'special': ['_.:,;!?&%$@#=/*+-',     1/8],
    }

    if not (args.upper or args.lower or args.number or args.special):
        for type in types.keys():
            args.__dict__[type] = True

    for type in types.keys():
        if args.__dict__[type]:
            charset += types[type][0]

        types[type][1] = round(args.length * (1 - types[type][1]))

    urandom = SystemRandom()

    while counter < args.amount:
        passwd = ''.join(urandom.sample(charset, k=args.length))

        if (args.upper or args.lower) and not (
            search(
                f"^[{types['lower'][0]}].*[{types['lower'][0]}]$", passwd,
                flags=IGNORECASE,
            )
        ):
            continue

        for type in types.keys():
            rest_length = len(sub(f'[{types[type][0]}]', '', passwd))

            if args.__dict__[type] and rest_length >= types[type][1]:
                continue

        print(passwd)

        counter += 1

    return 0

# ------------------------------------------------------------------------------

if __name__ == '__main__':
    return_value = main()

    exit(return_value)

Usage

~/dev/passwd-gen.py --amount=2 --length=20

Output:

piF%3?-u!wHo1Se6JL?n
C5_LRwE35+iY0=IuYqja