Mercurial > hgrepos > Python > apps > py-cutils
view cutils/genpwd.py @ 384:f66afecac253
treesum: add a generated .treesum file
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 17 May 2025 15:14:30 +0200 |
| parents | 48430941c18c |
| children |
line wrap: on
line source
# -*- coding: utf-8 -*- # :- # SPDX-FileCopyrightText: © 2018 Franz Glasner # SPDX-FileCopyrightText: © 2025 Franz Glasner # SPDX-License-Identifier: BSD-3-Clause # :- r"""A simple password generator to generate random passwords from selected character repertoires. Use :command:`genpwd.py --help' for a detailed help message. """ from __future__ import (division, absolute_import, print_function) import argparse import base64 import binascii import os import sys from . import (__version__, __revision__) # # Unreserved characters according to RFC 1738 (URL) **and** RFC 3986 (URI) # No general delimiters and no sub-delimiters. # WEB_CHARS = (b"ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwxyz" b"0123456789-._") # WEB_CHARS without visually similar characters (0O, 1lI) SAFE_WEB_CHARS = (b"ABCDEFGHJKLMNPQRSTUVWYXZabcdefghijkmnopqrstuvwxyz" b"23456789-._") # SAFE_WEB_CHARS with preference to punctuation SAFE_WEB_CHARS_2 = b".-_" + SAFE_WEB_CHARS # Unreserved characters from URI but with sub-delims allowed URI_CHARS = WEB_CHARS + b"~" + b"!$&'()*+,;=" # URI_CHARS without visually similar characters SAFE_URI_CHARS = SAFE_WEB_CHARS + b"~" + b"!$&'()*+,;=" # Just like SAFE_URI_CHARS but prefers punctuation characters SAFE_URI_CHARS_2 = (b"~" + b"!$&'()*+,;=" + SAFE_WEB_CHARS + b"~" + b"!$&'()*+,;=") # All visible characters from ASCII character set but no space FULL_ASCII = (b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" b"abcdefghijklmnopqrstuvwxyz!#$%&/()*+-.,:;<=>?@^_`[\\]{|}'\"~") # # A safer variant of FULL_ASCII: # - no characters that are visually similar (0O, 1lI) # - no characters with dead keys on german keyboards # - no backslash (too easily interpret as escape character # - no single or double quotes SAFE_ASCII = (b"23456789ABCDEFGHJKLMNPQRSTUVWXYZ" b"abcdefghijkmnopqrstuvwxyz!#$%&/()*+-.,:;<=>?@_[]{|}~") # just numeric and alphabetic ALNUM = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" # safer alpha-numberic without visually similar characters SAFE_ALNUM = b"23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" PY2 = sys.version_info[0] <= 2 def main(argv=None): aparser = argparse.ArgumentParser( description="A simple password generator for passwords with a given" " length within a selected character repertoire", fromfile_prefix_chars='@') aparser.add_argument( "-v", "--version", action="version", version="%s (rv:%s)" % (__version__, __revision__)) group = aparser.add_mutually_exclusive_group() group.add_argument( "--algorithm", "-a", choices=("web", "safe-web", "safe-web-2", "uri", "safe-uri", "safe-uri-2", "ascii", "safe-ascii", "alnum", "safe-alnum", "bin-base64", "bin-urlsafe-base64", "bin-base32", "bin-hex", "bin-ascii85",), default="safe-ascii", help=""" Select an algorithm and (implicitly) a character repertoire. All repertoires that start with `bin-' just encode the output of "os.urandom()" with the selected encoder. All repertoires that end with `-safe' or `safe-2' do not contain visually similar characters (currently `0O' or `Il1'). All repertoires that end with `-2' are variants with a bias to punctuation characters. This is incompatible with option `--repertoire'. Default: safe-ascii""") group.add_argument( "--repertoire", "-r", action="store", metavar="REPERTOIRE", help=""" Select from given character repertoire. The repertoire must be characters from the ISO-8859-15 character set. An empty REPERTOIRE selects implicitly the default algorithm. This is incompatible with option `--algorithm'.""") 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( "--group", "-G", action="store_true", help="""Group the results. If no "--group-sep" or "--group-size" is given use their respective defaults.""") aparser.add_argument( "--group-sep", action="store", default=None, metavar="GROUP-SEP", help="""Group the result using GROUP-SEP as separator. Option "--group" is implied. Default when grouping is enabled is the SPACE character ` '.""") aparser.add_argument( "--group-size", action="store", type=int, default=None, metavar="GROUP-SIZE", help="""Group the result using a group size of GROUP-SIZE characters. Option "--group" is implied. Default when grouping is enabled is 6.""") aparser.add_argument( "req_length", metavar="OUTPUT-LENGTH", type=int, help="The required length of the generated output") opts = aparser.parse_args(args=argv) grouper = None if opts.group or opts.group_sep is not None or opts.group_size is not None: if opts.group_sep is None: gsep = b' ' else: if PY2: gsep = opts.group_sep else: gsep = opts.group_sep.encode("utf8") gsize = 6 if opts.group_size is None else opts.group_size grouper = make_grouper(sep=gsep, size=gsize) if opts.repertoire: try: repertoire = (opts.repertoire if PY2 else opts.repertoire.encode("iso-8859-15")) except UnicodeError: raise ValueError("non ISO-8859-15 character in given repertoire") pwd = gen_from_repertoire(opts.req_length, repertoire, grouper=grouper) elif opts.algorithm == "web": pwd = gen_from_repertoire( opts.req_length, WEB_CHARS, grouper=grouper) elif opts.algorithm == "safe-web": pwd = gen_from_repertoire( opts.req_length, SAFE_WEB_CHARS, grouper=grouper) elif opts.algorithm == "safe-web-2": pwd = gen_from_repertoire( opts.req_length, SAFE_WEB_CHARS_2, grouper=grouper) elif opts.algorithm == "uri": pwd = gen_from_repertoire( opts.req_length, URI_CHARS, grouper=grouper) elif opts.algorithm == "safe-uri": pwd = gen_from_repertoire( opts.req_length, SAFE_URI_CHARS, grouper=grouper) elif opts.algorithm == "safe-uri-2": pwd = gen_from_repertoire( opts.req_length, SAFE_URI_CHARS_2, grouper=grouper) elif opts.algorithm == "ascii": pwd = gen_from_repertoire( opts.req_length, FULL_ASCII, grouper=grouper) elif opts.algorithm == "safe-ascii": pwd = gen_from_repertoire( opts.req_length, SAFE_ASCII, grouper=grouper) elif opts.algorithm == "alnum": pwd = gen_from_repertoire(opts.req_length, ALNUM, grouper=grouper) elif opts.algorithm == "safe-alnum": pwd = gen_from_repertoire( opts.req_length, SAFE_ALNUM, grouper=grouper) elif opts.algorithm == "bin-base64": encoder = base64.b64encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, rstrip_chars=b"=", grouper=grouper) elif opts.algorithm == "bin-urlsafe-base64": encoder = base64.urlsafe_b64encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, rstrip_chars=b"=", grouper=grouper) elif opts.algorithm == "bin-base32": encoder = base64.b32encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, rstrip_chars=b"=", grouper=grouper) elif opts.algorithm == "bin-ascii85": encoder = base64.a85encode pwd = gen_bin( opts.req_length, opts.use_bin_length, encoder, grouper=grouper) elif opts.algorithm == "bin-hex": encoder = binascii.hexlify pwd = gen_bin( opts.req_length, opts.use_bin_length, encoder, grouper=grouper) else: raise NotImplementedError("algorithm not yet implemented: %s" % opts.algorithm) if opts.group or opts.group_size or opts.group_sep: if len(pwd) < opts.req_length: raise AssertionError("internal length mismatch") else: 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 PY2: print(pwd) sys.stdout.flush() else: sys.stdout.buffer.write(pwd) sys.stdout.buffer.write(b'\n') sys.stdout.buffer.flush() def gen_from_repertoire(length, repertoire, grouper=None): """Select `length` characters randomly from given character repertoire `repertoire`. """ assert len(repertoire) <= 256 pwd = [] while len(pwd) < length: rndbytes = os.urandom(16) for c in rndbytes: if PY2: c = ord(c) if c < len(repertoire): pwd.append(repertoire[c]) if len(pwd) >= length: break if PY2: pwd = b''.join(pwd) else: pwd = bytes(pwd) if grouper: pwd = grouper(pwd) return pwd def gen_bin(length, use_bin_length, encoder, rstrip_chars=None, grouper=None): """Generate from :func:`os.urandom` and just encode with given `encoder`. """ pwd = encoder(os.urandom(length)) pwd = pwd.rstrip(rstrip_chars) if use_bin_length else pwd[:length] if grouper: pwd = grouper(pwd) return pwd def make_grouper(sep=b' ', size=6): def _grouper(pwd): if not pwd or size <= 0: return pwd assert isinstance(pwd, bytes) groups = [] idx = 0 while idx < len(pwd): groups.append(pwd[idx:idx+size]) idx += size return sep.join(groups) assert isinstance(sep, bytes) return _grouper if __name__ == "__main__": main()
