# HG changeset patch # User Franz Glasner # Date 1739444252 -3600 # Node ID 655f9e4bc6f212aab96bf947d31038bf5d669439 # Parent 4314ee20927a9883ee77ebcd4055bc42e41c6177 genpwd: allow grouping of the output diff -r 4314ee20927a -r 655f9e4bc6f2 cutils/genpwd.py --- a/cutils/genpwd.py Sat Feb 08 15:09:32 2025 +0100 +++ b/cutils/genpwd.py Thu Feb 13 11:57:32 2025 +0100 @@ -101,11 +101,38 @@ 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 @@ -113,54 +140,69 @@ 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) + pwd = gen_from_repertoire(opts.req_length, repertoire, grouper=grouper) elif opts.algorithm == "web": - pwd = gen_from_repertoire(opts.req_length, WEB_CHARS) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + pwd = gen_from_repertoire( + opts.req_length, SAFE_ASCII, grouper=grouper) elif opts.algorithm == "alnum": - pwd = gen_from_repertoire(opts.req_length, 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) + 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"=") + 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"=") + 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"=") + rstrip_chars=b"=", grouper=grouper) elif opts.algorithm == "bin-ascii85": encoder = base64.a85encode - pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder) + 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) + 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.use_bin_length: + if opts.group or opts.group_size or opts.group_sep: if len(pwd) < opts.req_length: raise AssertionError("internal length mismatch") else: - if len(pwd) != opts.req_length: - raise AssertionError("internal length mismatch") + 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() @@ -170,7 +212,7 @@ sys.stdout.buffer.flush() -def gen_from_repertoire(length, repertoire): +def gen_from_repertoire(length, repertoire, grouper=None): """Select `length` characters randomly from given character repertoire `repertoire`. @@ -190,15 +232,37 @@ 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): +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)) - return pwd.rstrip(rstrip_chars) if use_bin_length else pwd[: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__":