#!/usr/bin/python # (C) Kim Holburn 2003, 2011, 2016 # released under GNU Public License http://www.gnu.org/copyleft/gpl.html # script to generate pseudo-random strings # rewritten in python # expecting python 2.7 # version 1.4 2016-06-20 import argparse import string import random import re from os import urandom from itertools import chain def main(): parser = argparse.ArgumentParser( description="generate random string" """ Examples: The way I normally use this is: %(prog)s (generate a password of random characters) %(prog)s -w (generate a random wordlike phrase) Other possibilities: %(prog)s -g llldudx -c 30 %(prog)s +g dy -c 20 -t 10 %(prog)s +G "--__" -c 30 %(prog)s -w """, epilog=""" Default is normal mode default for normal mode is: %(prog)s -g lllldddYYYsss -c 12 -t 1 default for wordy mode is: %(prog)s -w -W 4 -m 2 -x 7 -t 1 -s " " """, prefix_chars='-+', prog='randstring', usage='%(prog)s [options]', formatter_class=argparse.RawTextHelpFormatter, conflict_handler='resolve') parser.add_argument("-h", "--help", action="store_true", dest="help", default=False, help="show this help screen and exit") parser.add_argument("-v", "--verbose", action="count", dest="verbose", default=0, help="print more status messages to stdout") parser.add_argument("-t", "--times", action="store", dest="times", type=int, default=1, help="number of times to run (default=%(default)s)") parser.add_argument("-N", "--neol", action="store_false", dest="eol", default=True, help="Send no end of line at end (default=eol)") normalg = parser.add_argument_group('normalg', description="Normal mode: " "The following options imply normal mode. ") normalg.add_argument("-n", "--normal", action="store_true", dest="normal", default=False, help="Normal mode [default]") normalg.add_argument("-c", "--chars", action="store", dest="charn", type=int, default=0, help="number of characters to output (default=12)") normalg.add_argument("-g", "--group", # action="store", dest="group", type=str, default='', action="append", dest="group", nargs=1, help="set groups of characters default:'lllldddYYYsss'" r""" "character groups" can include any combination of : l = lower case chars u = upper case chars d = digit chars h = hex digits H = uppercase hex digits s = space w = '#' and '@' (normally not in passwords) x = extended chars: !"$&'()<>[]{}*+-./%%:;=?\^_`|~ or instead of x you could use p = punctuation "'`$&%%*+-./:;=!?^_|~ y = limited set of punctuation '+,-./:;_~' Y = limited set of punctuation '-._' X = extra (brackets and stuff) ()<>[]{} """ ) normalg.add_argument("+g", "--addtogroup", action="store", dest="groupadd", type=str, default='', help="additional groups of characters") normalg.add_argument("-G", "--add-chars", action="store", dest="charadd", type=str, default='', help="additional characters to list") normalg.add_argument("-p", "--presets", action="store", dest="preset", type=int, default=0, choices=xrange(1, 5), help=("presets (default=1): \n" "1 (-g lllldddYYYsss -c 12 -t 1)\n" "2 (-g lllldddYY -c 12 -t 1)\n" "3 (-g lllld -c 8 -t 1)" "4 (-g H -c 20 -t 1)") ) wordyg = parser.add_argument_group('wordyg', description="Wordy mode: " "make word-like random output. " "The following options imply wordy mode. ") wordyg.add_argument("-w", "--wordy", action="store_true", dest="wordy", default=False, help="wordy mode") wordyg.add_argument("-m", "--min", action="store", dest="wmin", type=int, default=0, help="minimum word length (default=2)") wordyg.add_argument("-x", "--max", action="store", dest="wmax", type=int, default=0, help="maximum word length (default=7)") wordyg.add_argument("-W", "--words", action="store", dest="words", type=int, default=0, help="number of words (default=4)") wordyg.add_argument("-s", "--spaces", action="store", dest="spaces", type=str, default='', help="space characters (default=' ')" r""" more than one space character will be used randomly between each word """ ) wordyg.add_argument("-S", "--no-space", action="store", dest="nspace", type=str, help="no space - one of the space characters can represent no space" r""" It's hard to explain this option. add a letter like say 3 to the space list then add -S "3" and whenever the 3 gets chosen as the space, the program adds no space. In part this option exists because some argument parsers and OSes make it hard to enter an empty string for spaces. Not mentioning any names argpaser! """ ) wordyg.add_argument("-i", "--inword", action="store_true", dest="inword", default=False, help="Add inword punctuation ' or - (default=False)") # (options, args) = parser.parse_args() args = parser.parse_args() # if len(parser.argparser.REMAINDER) != 0: # parser.error("too many arguments") if args.help: parser.print_help() print exit() times = 1 eol = args.eol eolc = '\n' if not eol: eold = '' verbose = args.verbose normal = True charn = 12 group = 'lllldddYYYsss' preset = 1 charadd = '' groupadd = '' preset = 1 added = '' chars = '' presetl = ['lllldddYYYsss', 'lllldddYY', 'lllld', 'H'] presetc = [12, 12, 8, 20] presetn = [1, 1, 1, 1] output = '' wordy = False wmax = 7 wmin = 2 words = 4 spaces = ' ' nspace = '' inword = False # string.digits, string.lowercase, string.uppercase, vowels = "aeiouy" consonants = "bcdfghjklmnpqrstvwxyz" hexu = '0123456789ABCDEF' hexl = hexu.lower() VOWELS = vowels.upper() CONSONANTS = consonants.upper extended = r"""!"$%&'*+,-./\:;=?<>[](){}^_`|~""" punct = r"""!"$%&'*+,-./:;=?^_`|~""" limited = r"""+,-./:;_~""" limited1 = "-._" extra = r"""()<>[\]{}""" weird = r"""@#""" inpun = "'-" if 1 < verbose: print type(args) print "options:" for key, value in vars(args).items(): print " {0}:({1})".format (key, value) # decide which mode if args.normal and args.wordy: print "Error: Must chose only one of normal mode or wordy mode" exit(1) wordy_opts = args.wmax or args.wmin or args.words or args.spaces or args.nspace or args.inword normal_opts = args.charn or args.group or args.preset or args.groupadd or args.charadd nonpreset_opts = args.charn or args.group or args.groupadd or args.charadd if wordy_opts and normal_opts: print "Error: only use normal with normal options or wordy with wordy options" exit (1) if not (args.normal or args.wordy): # neither mode specified if wordy_opts: # try to work out mode using options wordy=True normal=False else: # by elimination, now we have one of each wordy = args.wordy normal = args.normal if wordy and normal_opts: print "Error: Specified wordy mode but also normal options" exit (1) elif normal and wordy_opts: print "Error: Specified normal mode but also wordy options" exit (1) if not args.eol: eolc = '' if 1 < args.times: times = args.times if normal: if 1 < args.preset: # if nonpreset_opts # Print "Error: please use presets by themselves" # exit (2) preset = args.preset if times == 1: times = presetn[preset - 1] # don't change times if it's already been specified charn = presetc[preset - 1] group = presetl[preset - 1] if args.charn: if 1 < args.charn: charn = args.charn if args.group: args_group = ''.join(chain(*args.group)) if args_group: if re.search(r"[^ludxXpswyYhH]",args_group) : print "Error: only 'ludxXpswyYhH' allowed in group list" exit (1) group = args_group if args.groupadd: if re.search(r"[^ludxXpswyYhH]",args.groupadd): print "Error: only 'ludxXpswyYhH' allowed into group list" exit (1) group += args.groupadd if args.charadd: if re.search(r"[^\x20-\x7E]",args.charadd): # stupid python doesn't have [:print:] or even \p{Print} character classes print "Error: only visible allowed to add to list" exit (1) added += args.charadd for g in group: if g == 'l': chars += string.lowercase elif g == 'u': chars += string.uppercase elif g == 'd': chars += string.digits elif g == 'x': chars += extended elif g == 'X': chars += extra elif g == 'p': chars += punct elif g == 's': chars += " " elif g == 'w': chars += weird elif g == 'y': chars += limited elif g == 'H': chars += hexu elif g == 'h': chars += hexl if added : chars += added random.seed(urandom(5)) for i in xrange(times): output += ''.join([random.choice(chars) for _ in xrange(charn)]) + eolc else: # wordy if 0 < args.wmin and args.wmin != 2 : wmin = args.wmin if 0 < args.wmax and args.wmax != 7 : if args.wmax < args.wmin : print "Error: max must be larger than min" exit (1) wmax = args.wmax if args.words and args.words != 4: words = args.words if args.spaces: if re.search(r"[^\x20-\x7E]", args.spaces): # stupid python doesn't have [:print:] or even \p{Print} character classes print "Error: spaces must be printable characters" exit (1) spaces = args.spaces if args.nspace: if len (args.nspace) != 1: print "Error: nspace must be a single character" exit (1) if re.search ( r"[^\x20-\x7E]", args.nspace): # stupid python doesn't have [:print:] or even \p{Print} character classes print "Error: nspace must be a printable character" exit (1) if not spaces: # stupid python doesn't have [:print:] or even \p{Print} character classes print "Error: nspace only useful if spaces" exit (1) if not args.nspace in spaces: print >>sys.stderr, "Warning: nspace is not in spaces" nspace = args.nspace if args.inword: inword = True # start wordy def spacer (): spaced = '' if len(spaces) == 0: return '' if len (spaces) == 1: spaced = spaces else: spaced = random.choice(spaces) if spaced == nspace: spaced = '' return spaced def lett (mystring): return random.choice(mystring) def makeword (size): # make word outw = "" cv = True pun = inword tpun = False ipun = size * 2 + 5 didpun = 0 tcv = bool(random.randint(0,2)) if 1 < verbose: print "# size({0}) ".format(size) for i in xrange(size): ch = lett (inpun if tpun else consonants if cv else vowels) if 1 < verbose: print "# i({0}) ch({1}) ipun({2}) tpun({3}) cv({4}) tcv({5})".format(i,ch,ipun,tpun,cv,tcv) outw += ch if tpun : ipun = 1000 didpun += 1 tpun = False if 1 < verbose: print "# ipun({0}) didpun({1}) i({2}) size-1({3})".format(ipun,didpun,i,size-1) if inword and i < (size - 2) and not bool (random.randint(0,ipun)): # we are adding in word punctuation and it's not the last char # and low probability tpun = True else: cv = not tcv tcv = cv == bool(random.randint(0,2)) return outw dist = wmax - wmin output = "" random.seed(urandom(5)) for i in xrange(times): for i in xrange(words): wordl = wmin if dist: wordl += random.randint(0,dist) if len(output): output += spacer() output += makeword(wordl) output += eolc if verbose: print "options:" print " {0}:{1}".format ('verbose',verbose) if 1 < verbose: print " {0}:{1}".format ('help',args.help) if 1 < verbose or times != 1: print " {0}:{1}".format ('times',times) if 1 < verbose or not eol: print " {0}:{1}".format ('eol',eol) print " {0}:{1}".format('normal', normal) if 1 < verbose or charn != 12: print " {0}:{1}".format(' charn', charn) if 1 < verbose or preset != 1: print " {0}:{1}".format(' preset', preset) if 1 < verbose or group != 'lllldddYYYsss': print " {0}:{1}".format(' group', group) if 1 < verbose or groupadd != '': print " {0}:{1}".format(' groupadd', groupadd) if 1 < verbose or normal: print " {0}:{1}".format(' chars', chars) print " {0}:{1}".format('wordy', wordy) if 1 < verbose or wmin != 2: print " {0}:{1}".format(' wmin', wmin) if 1 < verbose or wmax != 7: print " {0}:{1}".format(' wmax', wmax) if 1 < verbose or words != 4: print " {0}:{1}".format(' words', words) if 1 < verbose or inword: print " {0}:{1}".format(' inword', inword) if 1 < verbose or spaces != ' ': print " {0}:{1}".format(' spaces', spaces) if 1 < verbose or nspace != '': print " {0}:{1}".format(' nspace', nspace) print output, if __name__ == "__main__": main()