Mercurial > hgrepos > Python > apps > py-cutils
view cutils/genpwd.py @ 246:c29266444003
genpwd: Implement "uri"
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 08 Feb 2025 11:43:33 +0100 |
| parents | 35c06dcca856 |
| children | 435687f4e071 |
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 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 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", "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 from a character repertoire. All repertoires that start with "bin-" just encode the output of "os.urandom()" with the selected encoder. 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_from_repertoire(opts.req_length, WEB_CHARS) elif opts.repertoire == "safe-web": pwd = gen_from_repertoire(opts.req_length, SAFE_WEB_CHARS) elif opts.repertoire == "safe-web-2": pwd = gen_from_repertoire(opts.req_length, SAFE_WEB_CHARS_2) elif opts.repertoire == "uri": pwd = gen_from_repertoire(opts.req_length, URI_CHARS) elif opts.repertoire == "safe-uri": pwd = gen_from_repertoire(opts.req_length, SAFE_URI_CHARS) elif opts.repertoire == "safe-uri-2": pwd = gen_from_repertoire(opts.req_length, SAFE_URI_CHARS_2) elif opts.repertoire == "ascii": pwd = gen_from_repertoire(opts.req_length, FULL_ASCII) elif opts.repertoire == "safe-ascii": pwd = gen_from_repertoire(opts.req_length, SAFE_ASCII) elif opts.repertoire == "alnum": pwd = gen_from_repertoire(opts.req_length, ALNUM) elif opts.repertoire == "safe-alnum": pwd = gen_from_repertoire(opts.req_length, SAFE_ALNUM) elif opts.repertoire == "bin-base64": encoder = base64.b64encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, rstrip_chars=b"=") elif opts.repertoire == "bin-urlsafe-base64": encoder = base64.urlsafe_b64encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, rstrip_chars=b"=") elif opts.repertoire == "bin-base32": encoder = base64.b32encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder, rstrip_chars=b"=") elif opts.repertoire == "bin-ascii85": encoder = base64.a85encode pwd = gen_bin(opts.req_length, opts.use_bin_length, encoder) elif opts.repertoire == "bin-hex": encoder = binascii.hexlify 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_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()
