#!/usr/bin/python3 """generate word compound passwords usage: pwgen -c [number_of_phrases default=1] or pwgen [-s | -S | -n | -p | -/] [-L len default=20] [number_of_phrases default=1] """ import random, sys from enum import Enum class Padding(Enum): spaces = 0 dots = 1 punct = 2 numbers = 3 camel = 4 snake = 5 class Config: def __init__(self): self.padding = Padding.spaces self.first_punct = False self.last_punct = False self.first_number = False self.last_number = False self.capitalize = False self.use_conjunctions = False self.target_len = 20 # default pw len self.number = 1 def __repr__(self): return(str(list((repr(self.padding), self.first_punct, self.last_punct, self.first_number, self.last_number, self.capitalize, self.target_len, self.number, self.use_conjunctions)))) SPACE = " " DOTS = ".," PUNCTS = """ !@#$%^&*()-_=+[{]}\\|;:/?.>,<~""" NUMBERS = "0123456789" CONJUNCTIONS = ["and", "and a", "and the","or", "or the", "or a", "with a", "with", "with the", "by a", "by the", "on a", "on the","on", "in a", "in the", "in", "for", "for a", "for the", "but", "so"] def toInt(s, chkRange, errStr = "sorry, that's not a number", rangeStr = "sorry, I can't do that number"): try: t = int(s) except: print (errStr) sys.exit() if tchkRange[1]: print (rangeStr) sys.exit() return t def get_args(conf): narg = 1 nargs = len(sys.argv) while narg make pass phrases at least this long; default=20 num_phrases make multiple pass phrases, one per line; default=1\n""") sys.exit() elif a[0]=='-': for b in a[1:]: match b: case 'c': conf.first_punt = True case 'C': conf.last_punct = True case 'n': conf.first_number = True case 'N': # add number conf.last_number = True case 's': conf.padding = Padding.spaces case 'S': conf.padding = Padding.dots case 'p': conf.padding = Padding.punct case 'P': conf.padding = Padding.numbers case 'k': conf.padding = Padding.camel case 'K': conf.padding = Padding.snake case 'M': conf.capitalize = True case 'X': conf.capitalize = True conf.last_punct = True conf.last_number = True conf.padding = Padding.dots case 'a': conf.use_conjunctions = True; case 'L': narg = narg + 1 if narg < nargs: conf.target_len = toInt(sys.argv[narg],[12,66],rangeStr = "Sorry, I can't build phrases that long") case _: print(f">>> unexpected command option '{b}'") else: if narg < nargs: conf.number = toInt(sys.argv[narg],[0,40],rangeStr = "Sorry, I can't print that many; try a smaller number") # pick off the number of phrases narg = narg + 1 def load_words(): f = open("/usr/share/dict/words") #f = open("/usr/share/dict/corncob_lowercase.txt") d = f.read().splitlines() f.close() d = list(filter(lambda x : len(x)<10 and not x.endswith("'s"), d)) return d def get_word(conf, data): m = data[random.randint(0,len(data)-1)] if conf.capitalize or conf.padding == Padding.camel: m = m.capitalize() return m def pwgen(conf, data): w = [get_word(conf, data)] nExtraChars = conf.first_punct+conf.last_punct+conf.first_number+conf.last_number # TODO: we need a length estimator for this loop while sum(len(a) for a in w) + (len(w)-1 if not conf.padding==Padding.camel else 0) + nExtraChars < conf.target_len: if conf.use_conjunctions == True: c = CONJUNCTIONS[random.randint(0,len(CONJUNCTIONS)-1)] w.extend(c.split()) w.append(get_word(conf, data)) if w[-2] == 'a' and w[-1][0] in "aeiouAEIOU": w[-2] = "an" # change so that two-word conjunctions are multiple list elements if conf.padding == Padding.camel: ret = "".join(w) elif conf.padding == Padding.spaces: ret = SPACE.join(w) elif conf.padding == Padding.dots: p = DOTS[random.randint(0,len(DOTS)-1)] ret = p.join(w) elif conf.padding == Padding.numbers: n = NUMBERS[random.randint(0,len(NUMBERS)-1)] ret = n.join(w) elif conf.padding == Padding.punct: p = PUNCTS[random.randint(0,len(PUNCTS)-1)] ret = p.join(w) elif conf.padding == Padding.snake: ret = '_'.join(w) n = NUMBERS[random.randint(0,len(NUMBERS)-1)] if conf.first_number: ret = n + ret if conf.last_number: ret = ret + n p = PUNCTS[random.randint(0,len(PUNCTS)-1)] if conf.first_punct: ret = p + ret if conf.last_punct: ret = ret + p return ret if __name__ == "__main__": conf = Config() get_args(conf) data = load_words() for i in range(conf.number): pw = pwgen(conf, data) print(pw) """ although this read n; i="1"; while [ $i -le $n ]; do cat /etc/dictionaries-common/words | shuf | head -1; i=$(( $i + 1 )); done will do the job almost as well NEW PLAN -s spaces (default) -S comma+period -p punctuation -P number -k CamelCase -K SnakeCase (only one of these 6 allowed) -c special char at beginning -C special char at end -n number at beginning -N number at end -M capitalize all words -a fill with conjunctions -X equivalent to -SCNM -L length number of samples -k -a not recommended -s dog cat mouse -S dog.cat.mouse -p dog#cat#mouse -P dog4cat4mouse -c !dog cat mouse -C dog cat mouse# -n 4dog cat mouse -N dog cat mouse4 -cnCN $4dog cat mouse4$ -M Dog Cat Mouse -k DogCatMouse -kN DogCatMouse2 -K dog_cat_mouse -KMN Dog_Cat_Mouse3 -a dog and a cat with mouse -Sa dog.and.a.cat.with.mouse -pa dog#and#a#cat#with#mouse -paCN dog^and^a^cat^with^mouse5% -X Dog.Cat.Mouse6& leet encoder a->4A e->3 i->I o->0 s->5$ t->7 """