view cutils/genpwd.py @ 237:d65a4d861dad

Enhance help message
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 08 Feb 2025 08:53:18 +0100
parents 939a8da6bc92
children ff13b2a863ba
line wrap: on
line source

# -*- coding: utf-8 -*-
# :-
# :Copyright: (c) 2018 Franz Glasner
# :Copyright: (c) 2025 Franz Glasner
# :License:   BSD-3-Clause
# :-
r"""Generate passwords.

Usage: genpwd.py [ Options ] required_length

Options:

  --type, -t    web, web-safe, web-safe2, base64, base32, ascii85

:Author:  Franz Glasner

"""

from __future__ import (division, absolute_import, print_function)

import argparse
import base64
import os
import sys

from . import (__version__, __revision__)


WEB_CHARS = b"ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwxyz" \
            b"0123456789.,-_;!()[]{}*"
WEB_SAFE_CHARS = b"ABCDEFGHJKLMNPQRSTUVWYXZabcdefghijkmnopqrstuvwxyz" \
                 b"23456789.,-_;!"
WEB_SAFE2_CHARS = b".,-_;!" + WEB_SAFE_CHARS   # prefer punctionation chars

PY2 = sys.version_info[0] <= 2


def main(argv=None):
    aparser = argparse.ArgumentParser(
        description="A simple password generator for password of a given"
                    " length within a character repertoire",
        fromfile_prefix_chars='@')
    aparser.add_argument(
        "--version", "-v", action="version",
        version="%s (rv:%s)" % (__version__, __revision__))
    aparser.add_argument(
        "-E", dest="use_bin_length", action="store_true",
        help="For some repertoires make OUTPUT-LENGTH the number of bytes"
             " that is to be read from random sources instead of output bytes")
    aparser.add_argument(
        "--repertoire", "--type", "-t",
        choices=("web", "web-safe", "web-safe2",
                 "base64", "urlsafe-base64", "urlsafe", "base32", "ascii85", ),
        default="web-safe2",
        help="Select the character repertoire. Default: web-safe2")
    aparser.add_argument(
        "req_length", metavar="OUTPUT-LENGTH", type=int,
        help="The required length of the generated output")

    opts = aparser.parse_args(args=argv)

    if opts.repertoire == "web":
        pwd = gen_web(opts.req_length, WEB_CHARS)
    elif opts.repertoire == "web-safe":
        pwd = gen_web(opts.req_length, WEB_SAFE_CHARS)
    elif opts.repertoire == "web-safe2":
        pwd = gen_web(opts.req_length, WEB_SAFE2_CHARS)
    elif opts.repertoire == "base64":
        encoder = base64.b64encode
        pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder,
                      rstrip_chars=b"=")
    elif opts.repertoire in ("urlsafe-base64", "urlsafe"):
        encoder = base64.urlsafe_b64encode
        pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder,
                      rstrip_chars=b"=")
    elif opts.repertoire == "base32":
        encoder = base64.b32encode
        pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder,
                      rstrip_chars=b"=")
    elif opts.repertoire == "ascii85":
        encoder = base64.a85encode
        pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder)
    else:
        raise NotImplementedError("type not yet implemented: %s"
                                  % opts.repertoire)
    if opts.use_bin_length:
        if len(pwd) < opts.req_length:
            raise AssertionError("internal length mismatch")
    else:
        if len(pwd) != opts.req_length:
            raise AssertionError("internal length mismatch")
    if not PY2:
        pwd = pwd.decode("ascii")
    print(pwd)


def gen_web(length, chars):
    mult = 256//len(chars)
    repertoire = chars * mult
    assert len(repertoire) <= 256
    pwd = []
    while len(pwd) < length:
        c = os.urandom(1)
        if PY2:
            c = ord(c)
        else:
            c = c[0]
        if c < len(repertoire):
            pwd.append(repertoire[c])
    if PY2:
        pwd = b''.join(pwd)
    else:
        pwd = bytes(pwd)
    return pwd


def gen_bin(length, use_bin_length, encoder, rstrip_chars=None):
    pwd = encoder(os.urandom(length))
    return pwd.rstrip(rstrip_chars) if use_bin_length else pwd[:length]


if __name__ == "__main__":
    main()