pwgen.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. #!/usr/bin/python3
  2. """generate word compound passwords
  3. usage: pwgen -c [number_of_phrases default=1]
  4. or pwgen [-s | -S | -n | -p | -/] [-L len default=20] [number_of_phrases default=1]
  5. """
  6. import random, sys
  7. from enum import Enum
  8. class Padding(Enum):
  9. spaces = 0
  10. dots = 1
  11. punct = 2
  12. numbers = 3
  13. camel = 4
  14. snake = 5
  15. class Config:
  16. def __init__(self):
  17. self.padding = Padding.spaces
  18. self.first_punct = False
  19. self.last_punct = False
  20. self.first_number = False
  21. self.last_number = False
  22. self.capitalize = False
  23. self.use_conjunctions = False
  24. self.target_len = 20 # default pw len
  25. self.number = 1
  26. def __repr__(self):
  27. return(str(list((repr(self.padding),
  28. self.first_punct, self.last_punct,
  29. self.first_number, self.last_number,
  30. self.capitalize,
  31. self.target_len, self.number,
  32. self.use_conjunctions))))
  33. SPACE = " "
  34. DOTS = ".,"
  35. PUNCTS = """ !@#$%^&*()-_=+[{]}\\|;:/?.>,<~"""
  36. NUMBERS = "0123456789"
  37. CONJUNCTIONS = ["and", "and a", "and the","or", "or the", "or a",
  38. "with a", "with", "with the", "by a", "by the",
  39. "on a", "on the","on", "in a", "in the", "in",
  40. "for", "for a", "for the"]
  41. # delete these
  42. number = 1
  43. targetLen = 20
  44. useConjunction = False
  45. add_punc = False
  46. add_number = False
  47. def toInt(s, chkRange, errStr = "sorry, that's not a number", rangeStr = "sorry, I can't do that number"):
  48. try:
  49. t = int(s)
  50. except:
  51. print (errStr)
  52. sys.exit()
  53. if t<chkRange[0] or t>chkRange[1]:
  54. print (rangeStr)
  55. sys.exit()
  56. return t
  57. def get_args(conf):
  58. narg = 1
  59. nargs = len(sys.argv)
  60. while narg<nargs:
  61. a = sys.argv[narg]
  62. if a=="-h" or a=="--help" or a=="--version":
  63. print ("pwgen.py v1.3 generate passphrase; inspired by xkcd/936 and SteveGibson\n")
  64. print ("""Usage: pwgen.py [-sSpPkKnNcCaMX] [-L length] [num_phrases]
  65. pwgen.py -h | --help | --version
  66. -s pad with only spaces (default; for smartphone)
  67. -S pad with period and comma (for smartphone)
  68. -p pad with special characters
  69. -P pad with numbers
  70. -k connect the words as CamelCase
  71. -K connect the words as snake_case
  72. -n add a number at the beginning
  73. -N add a number at the end
  74. -c add a special character at the begnning
  75. -C add a special character at the end
  76. -M capitalize the words
  77. -a connect the words with a conjuction filler
  78. -X same as -MNCS (capital, periods & commas, extra number & char)
  79. -L <n> make pass phrases at least this long; default=20
  80. num_phrases make multiple pass phrases, one per line; default=1\n""")
  81. sys.exit()
  82. elif a[0]=='-':
  83. for b in a[1:]:
  84. match b:
  85. case 'c':
  86. conf.first_punt = True
  87. case 'C':
  88. conf.last_punct = True
  89. case 'n':
  90. conf.first_number = True
  91. case 'N': # add number
  92. conf.last_number = True
  93. case 's':
  94. conf.padding = Padding.spaces
  95. case 'S':
  96. conf.padding = Padding.dots
  97. case 'p':
  98. conf.padding = Padding.punct
  99. case 'P':
  100. conf.padding = Padding.numbers
  101. case 'k':
  102. conf.padding = Padding.camel
  103. case 'K':
  104. conf.padding = Padding.snake
  105. case 'M':
  106. conf.capitalize = True
  107. case 'X':
  108. conf.capitalize = True
  109. conf.last_punct = True
  110. conf.last_number = True
  111. conf.padding = Padding.dots
  112. case 'a':
  113. conf.use_conjunctions = True;
  114. case 'L':
  115. narg = narg + 1
  116. if narg < nargs:
  117. conf.target_len = toInt(sys.argv[narg],[12,66],rangeStr = "Sorry, I can't build phrases that long")
  118. case _:
  119. print(f">>> unexpected command option '{b}'")
  120. else:
  121. if narg < nargs:
  122. 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
  123. narg = narg + 1
  124. def load_words():
  125. f = open("/usr/share/dict/words")
  126. #f = open("/usr/share/dict/corncob_lowercase.txt")
  127. d = f.read().splitlines()
  128. f.close()
  129. d = list(filter(lambda x : len(x)<10 and not x.endswith("'s"), d))
  130. return d
  131. def get_word(conf, data):
  132. m = data[random.randint(0,len(data)-1)]
  133. if conf.capitalize or conf.padding == Padding.camel:
  134. m = m.capitalize()
  135. return m
  136. def pwgen(conf, data):
  137. w = [get_word(conf, data)]
  138. nExtraChars = conf.first_punct+conf.last_punct+conf.first_number+conf.last_number
  139. # TODO: we need a length estimator for this loop
  140. while sum(len(a) for a in w) + (len(w)-1 if not conf.padding==Padding.camel else 0) + nExtraChars < conf.target_len:
  141. if conf.use_conjunctions == True:
  142. c = CONJUNCTIONS[random.randint(0,len(CONJUNCTIONS)-1)]
  143. w.extend(c.split())
  144. w.append(get_word(conf, data))
  145. if w[-2] == 'a' and w[-1][0] in "aeiouAEIOU":
  146. w[-2] = "an"
  147. # change so that two-word conjunctions are multiple list elements
  148. if conf.padding == Padding.camel:
  149. ret = "".join(w)
  150. elif conf.padding == Padding.spaces:
  151. ret = SPACE.join(w)
  152. elif conf.padding == Padding.dots:
  153. p = DOTS[random.randint(0,len(DOTS)-1)]
  154. ret = p.join(w)
  155. elif conf.padding == Padding.numbers:
  156. n = NUMBERS[random.randint(0,len(NUMBERS)-1)]
  157. ret = n.join(w)
  158. elif conf.padding == Padding.punct:
  159. p = PUNCTS[random.randint(0,len(PUNCTS)-1)]
  160. ret = p.join(w)
  161. elif conf.padding == Padding.snake:
  162. ret = '_'.join(w)
  163. n = NUMBERS[random.randint(0,len(NUMBERS)-1)]
  164. if conf.first_number:
  165. ret = n + ret
  166. if conf.last_number:
  167. ret = ret + n
  168. p = PUNCTS[random.randint(0,len(PUNCTS)-1)]
  169. if conf.first_punct:
  170. ret = p + ret
  171. if conf.last_punct:
  172. ret = ret + p
  173. return ret
  174. if __name__ == "__main__":
  175. conf = Config()
  176. get_args(conf)
  177. data = load_words()
  178. for i in range(conf.number):
  179. pw = pwgen(conf, data)
  180. print(pw)
  181. """
  182. although this
  183. read n; i="1"; while [ $i -le $n ]; do cat /etc/dictionaries-common/words | shuf | head -1; i=$(( $i + 1 )); done
  184. will do the job almost as well
  185. NEW PLAN
  186. -s spaces (default)
  187. -S comma+period
  188. -p punctuation
  189. -P number
  190. -k CamelCase
  191. -K SnakeCase (only one of these 6 allowed)
  192. -c special char at beginning
  193. -C special char at end
  194. -n number at beginning
  195. -N number at end
  196. -M capitalize all words
  197. -a fill with conjunctions
  198. -X equivalent to -SCNM
  199. -L <n> length
  200. <n> number of samples
  201. -k -a not recommended
  202. -s dog cat mouse
  203. -S dog.cat.mouse
  204. -p dog#cat#mouse
  205. -P dog4cat4mouse
  206. -c !dog cat mouse
  207. -C dog cat mouse#
  208. -n 4dog cat mouse
  209. -N dog cat mouse4
  210. -cnCN $4dog cat mouse4$
  211. -M Dog Cat Mouse
  212. -k DogCatMouse
  213. -kN DogCatMouse2
  214. -K dog_cat_mouse
  215. -KMN Dog_Cat_Mouse3
  216. -a dog and a cat with mouse
  217. -Sa dog.and.a.cat.with.mouse
  218. -pa dog#and#a#cat#with#mouse
  219. -paCN dog^and^a^cat^with^mouse5%
  220. -X Dog.Cat.Mouse6&
  221. leet encoder a->4A e->3 i->I o->0 s->5$ t->7
  222. """