Mercurial > hgrepos > Python > apps > py-cutils
view cutils/genpwd.py @ 253:4314ee20927a
genpwd: more consistency with regard to help messages
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 08 Feb 2025 15:09:32 +0100 |
| parents | 0a2a162c5ad7 |
| children | 655f9e4bc6f2 |
line wrap: on
line source
# -*- coding: utf-8 -*- # :- # :Copyright: (c) 2018 Franz Glasner # :Copyright: (c) 2025 Franz Glasner # :License: 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( "req_length", metavar="OUTPUT-LENGTH", type=int, help="The required length of the generated output") opts = aparser.parse_args(args=argv) 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) elif opts.algorithm == "web": pwd = gen_from_repertoire(opts.req_length, WEB_CHARS) elif opts.algorithm == "safe-web": pwd = gen_from_repertoire(opts.req_length, SAFE_WEB_CHARS) elif opts.algorithm == "safe-web-2": pwd = gen_from_repertoire(opts.req_length, SAFE_WEB_CHARS_2) elif opts.algorithm == "uri": pwd = gen_from_repertoire(opts.req_length, URI_CHARS) elif opts.algorithm == "safe-uri": pwd = gen_from_repertoire(opts.req_length, SAFE_URI_CHARS) elif opts.algorithm == "safe-uri-2": pwd = gen_from_repertoire(opts.req_length, SAFE_URI_CHARS_2) elif opts.algorithm == "ascii": pwd = gen_from_repertoire(opts.req_length, FULL_ASCII) elif opts.algorithm == "safe-ascii": pwd = gen_from_repertoire(opts.req_length, SAFE_ASCII) elif opts.algorithm == "alnum": pwd = gen_from_repertoire(opts.req_length, ALNUM) elif opts.algorithm == "safe-alnum": pwd = gen_from_repertoire(opts.req_length, SAFE_ALNUM) elif opts.algorithm == "bin-base64": encoder = base64.b64encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, rstrip_chars=b"=") elif opts.algorithm == "bin-urlsafe-base64": encoder = base64.urlsafe_b64encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, rstrip_chars=b"=") elif opts.algorithm == "bin-base32": encoder = base64.b32encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, rstrip_chars=b"=") elif opts.algorithm == "bin-ascii85": encoder = base64.a85encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder) elif opts.algorithm == "bin-hex": encoder = binascii.hexlify pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder) else: raise NotImplementedError("algorithm not yet implemented: %s" % opts.algorithm) 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): """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) return pwd def gen_bin(length, use_bin_length, encoder, rstrip_chars=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] if __name__ == "__main__": main()
