123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- #!/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 t<chkRange[0] or t>chkRange[1]:
- print (rangeStr)
- sys.exit()
- return t
- def get_args(conf):
- narg = 1
- nargs = len(sys.argv)
- while narg<nargs:
- a = sys.argv[narg]
- if a=="-h" or a=="--help" or a=="--version":
- print ("pwgen.py v1.4 generate passphrase; inspired by xkcd/936 and SteveGibson\n")
- print ("""Usage: pwgen.py [-sSpPkKnNcCaMX] [-L length] [num_phrases]
- pwgen.py -h | --help | --version
- -s pad with only spaces (default; for smartphone)
- -S pad with period and comma (for smartphone)
- -p pad with special characters
- -P pad with numbers
- -k connect the words as CamelCase
- -K connect the words as snake_case
- -n add a number at the beginning
- -N add a number at the end
- -c add a special character at the begnning
- -C add a special character at the end
- -M capitalize the words
- -a connect the words with a conjuction filler
- -X same as -MNCS (capital, periods & commas, extra number & char)
- -L <n> 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 <n> length
- <n> 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
- """
|