pwgen.py 7.0 KB

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